6.1 普通 XSS 漏洞自动化挖掘思路
自动化的XSS漏洞挖掘其实是很复杂的,难度也会很高。这和我们要实现的XSS漏洞挖掘工具的需求有关,是要效率(有了广度,却忽略了深度),还是要检出率(既有广度,又有深度,漏洞个数多且准确度高)。如果要检出率,那么很可能就是实现了fuzzing模式的工具。效率与检出率是矛盾的,所以我们通常看到的具有商业性质的漏洞检测平台都会在这两点之间寻求一个平衡点,这种矛盾是业务带来的。
XSS漏洞挖掘有很多难点和有意思的地方,不是所有的漏洞挖掘都能很好地自动化,对于像特殊场景下的XSS挖掘等是需要人工参与的,虽然会借助一些辅助工具。还有一些是依赖浏览器特性或BUG导致的XSS,这些会在后面的章节里单独说明。
本节以反射型XSS挖掘为开篇,我们会详细介绍工具自动化的思路,这种思路是一种针对反射型XSS、存储型XSS、头部XSS、Cookie XSS等比较普通的XSS漏洞挖掘思路,而且它已经经过我们的工具化证明,非常有效。
6.1.1 URL上的玄机
我们知道这类XSS的输入点在URL上,URL的知识在2.2节中已介绍过,下面摘录部分进行介绍,URL的一种常见组成模式如下:
<scheme>://<netloc>/<path>?<query>#<fragment>
比如,一个最普通的URL如下:
http://www.foo.com/path/f.php?id=1&type=cool#new
对应关系如下:
<scheme> - http <netloc> - www.foo.com <path> - /path/f.php <query> - id=1&type=cool,包括<参数名=参数值>对 <fragment> - new
对这个URL来说,攻击者可控的输入点有<path>、<query>、<fragment>三个部分。这三部分对攻击者(或挖掘工具)来说,其意义非常明确。
那么看下面这个URL:
http://www.foo.com/path/1/id#new
也许攻击者可以知道是/path还是<path>,1是参数值,id是参数名,但是用工具如何知道?除非攻击者手工设置工具的URL识别模式,而且这种情况只可能是特例,下次出现这样的URL呢?
http://www.foo.com/path/type/cool#new
在工具自动化的过程中有一个非常重要的机制必须具备,就是这类路径型参数的识别,其实这部分不应该是XSS漏洞挖掘需要关心的,而应该是上层爬虫需要关心的。这是对爬虫的一种挑战,传统的Web中,每个URL对应具体的一个文件资源,而在Web 2.0时代,强调每个URL都必须具备非常明确的含义,好处不仅是便于人们阅读,而且也便于那些Google/Baidu爬虫理解与收录(SEO的手段之一)。比如,如下URL:
http://www.foo.com/20121221/world-will-be-ended
比下面的URL好:
http://www.foo.com/20121221/post.php?id=3
更有甚者,现在风靡RESTFUL风格的HTTPAPI,比如,微博的一些API:
http://weibo.com/apis/show_friends http://weibo.com/apis/delete_msg
每个URL已经具备了明确的含义,而且这些URL一般都是通过URL映射来实现资源访问的,这种映射很强大,能够精确到具体的一个函数接口(如果了解过Web框架式开发的人,比如Django里的urls.py配置的各种映射)。这个时候URL已经不再对应具体的文件资源了,甚至URL里的参数输入都不再有问号(?)标志。这就是Web 2.0带来的巨大革新之一。
爬虫必须能赶上这种革新,因为爬虫识别出URL每部分的差异时,不仅仅是对后续的漏洞挖掘有帮助,而且对于爬虫本身的一些策略动态调整也有帮助,比如,一些相似度高的URL其实是对应到了一个相同的资源,爬虫就没必要重复分析,如:
http://www.foo.com/page/1/id/2011 http://www.foo.com/id/2011/page/1
这里就不细说爬虫本身了,因为强大的爬虫不是一个简单的工程(包括写这些描述文字)。
回到XSS漏洞挖掘上,上面说了攻击者可控的输入点有<path>、<query>、<fragment>三个,其实<fragment>里的值一般不会出现在服务端解析,除非Web 2.0网站,比如twitter,它的URL格式如下:
http://twitter.com/evilcos!#status
请求时,第一步会通过JavaScript的loca-tion.href获取到完整的URL,并解析出status值,然后通过各种AJAX函数来处理请求,最后进行各种局部页面的异步刷新。用户体验很好,可是爬虫很抓狂。同样,对这部分的反射型XSS挖掘就困难了很多,我们先从简单的入手,这个<fragment>暂时跳过。
6.1.2 HTML中的玄机
在6.1.1节中,为了使问题简单化,我们忽略了URL的的Schemeccnetlocn与与fragmentf这几部分,就剩下<path>和<query>了,这样就没难度了吗?下面来分析一下。由于<path>和<query>的情况很相似,所以,下面以流行的<query>为例进行说明。
看下面一个普通的URL:
<script>alert(1)</script> '"><script>alert(1)</script> <img/src=@ onerror=alert(1)/> '"><img/src=@ onerror=alert(1)/> ' onmouseover=alert(1) x=' " onmouseover=alert(1) x=" onmouseover=alert(1) x= javascript:alert(1)// data:text/ html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg== '";alert(1)// </script><script>alert(1)// }x:expression(alert(1)) alert(1)// */-->'"></iframe></script></style></title></ textarea></xmp></noscript></noframes></ plaintext><script>alert(1)</script>
然后根据请求后的反应来看是否有弹出窗或者引起浏览器脚本错误。如果出现这些情况,就几乎可以认为目标存在XSS漏洞。这些payloads都很有价值,它们也存在很大的差异,玄机就出现在HTML中。
针对这个URL,我们利用的输入点是id=1,那么输出在哪里?可能有如下几处。
· HTML标签之间,比如:出现在<divid="body">[输出]</div>位置。
· HTML标签之内,比如:出现在<inputtype="text"value="[输出]"/>位置。
· 成为JavaScript代码的值,比如:<script>a="[输出]";...</script>位置。
· 成为CSS代码的值,比如:<style>body{font-size:[输出]px;...}</style>位置。
基本上就这以上四种情况,不过我们对这四种情况还可以细分,你会发现各种差异与陷阱。下面假设服务端不对用户输入与响应输出做任何编码与过滤。
1. HTML标签之间
最普通的场景出现在<div id="body">[输出]</div>位置,那么提交:
id=1<script>alert(1)</script>
就可以触发XSS了。可如果出现在下面这些标签中呢?
<title></title> <textarea></textarea> <xmp></xmp> <iframe></iframe> <noscript></noscript> <noframes></noframes> <plaintext></plaintext>
比如,代码<title><script>alert(1)</script></title>会弹出提示框吗?答案是:都不会!这些标签之间无法执行脚本。XSS漏洞挖掘机制必须具备这样的区分能力,比如,发现出现在<title></title>中,就将提交的payload变为:
</title><script>alert(1)</script>
除了这些,还有两类特殊的标签<script>和<style>,它们是不能嵌套标签的,而且payload构造情况会更灵活,除了闭合对应的标签外,还可以利用它们自身可执行脚本的性质来构造特殊的payload,这在下面介绍。
2. HTML标签之内
最普通的场景出现在<input type="text"value="[输出]"/>位置,要触发XSS,有以下两种方法:
· 提交payload:"onmouseover=alert(1) x=",这种是闭合属性,然后使用on事件来触发脚本。
· 提交payload:"><script>alert(1)</script>,这种是闭合属性后又闭合标签,然后直接执行脚本。
题外话:先来看看这两个payload哪个更好,如果对比利用效果,自然是第二个更好,因为它可直接执行。可是在工具挖掘中,哪个payload的成功率更高呢?从对比可知,第二个比第一个多了<>字符,而很多情况下,目标网站防御XSS很可能就过滤或编码了<>字符,所以第一个payload的成功率会更高,这也是漏洞挖掘工具在这个场景中必须优先使用的payload。换句话说,我们的工具必须知道目标环境的特殊性,然后进行针对性的挖掘,而不应该盲目。
下面继续看HTML标签之内的各种场景,如果出现下面的语句:
<input type="hidden" value="[输出]" />
一般情况下,此时我们只能闭合input标签,否则由于hidden特性导致触发不了XSS。如果出现下面的语句:
<input value="[输出]" type="hidden" />
和上面这个仅仅是两个属性的顺序不同而已。怎么才能出现高成功率的payload?语句如下:
1" onmouserover=alert(1) type="text
输出后变为:
<input value="1"onmouserover=alert(1) type="text" type="hidden" />
这时候的输出不再是一个隐藏的表单项,而是一个标准的输入框,鼠标移上去就可触发XSS。
下面我们来实践一下。
输出场景如下:
<input type="text" value="[输出]" disabled=1 />
怎么构造出一个成功率高的payload?
我们继续来看同样有意思的场景,比如这三类:
· 输出在src/href/action等属性内,比如<ahref="[输出]">click me</a>。
· 输出在on*事件内,比如<a href="#"onclick="[输出]">click me</a>。
· 输出在style属性内,比如<a href="#"style="[输出]">click me</a>。
1)输出在src/href/action等属性内
我们的payload除了各种闭合之外,还可以像下面这样:
javascript:alert(1)// data:text/ html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==
前提是我们提交的payload必须出现在这些属性值的开头部分(data:协议的必须作为整个属性值出现)。对于第一个javascript:伪协议,所有的浏览器都支持,不过有些差异;对于第二个data:协议,仅IE浏览器不支持。另外,我们提交的这两个payload是可以进行一些混淆的,这样可以更好地绕过过滤机制,这些差异与混淆机制都会放在后面介绍。
看javascript:alert(1)//这个payload的场景,如果输出以下语句:
<a href="javascript:alert(1)//html">click me</a>
那么点击后会正常触发。但在一次真实的挖掘过程中,我们发现有个网站居然过滤了/字符,而其他'"等特殊字符也都过滤了,怎么办?我们想到了利用JavaScript逻辑与算数运算符,因为JavaScript是弱类型语言。所以,如果出现字符串与字符串之间的各种运算是合法的。比如:
javascript:alert(1)-
输出后点击同样触发,只不过浏览器会报错,这样的错误是可以屏蔽的:
window.onerror = function(){return true;}
2)输出在on*事件内
由于on*事件内是可以执行JavaScript脚本的。根据不同的场景,我们需要弄清楚我们的输出是作为整个on*事件的值出现,还是以某个函数的参数值出现,这个函数是什么等。不同的出现场景可能需要不同的闭合策略,最终目标都是让我们的脚本能顺利执行。
最神奇的场景如下:
<a href="#" onclick="eval('[输出]')">click me</a>
那么,我们的payload只要提交alert(1)就可以。这种情况下,即使将那些特殊字符都过滤了,也同样可以成功触发XSS。
还有一点差异不得不提,HTML标签有几十种,它们支持的on事件却不尽相同,甚至在浏览器之间也出现了差异,所以实际攻击中需要进行区分。
3)输出在style属性内
我们知道,现在在style中执行脚本已经是IE浏览器独有的特性,曾经Firefox的moz-binding里是可以引用外部xml资源以执行JavaScript的,修修补补,总算是屏蔽了这些缺陷。对IE来说,在标签的style属性中只要能注入expression关键词,并进行适当的闭合,我们就可以认为目标存在XSS。比如注入:
1;xss:expression(if(!window.x) {alert(1);window.x=1;})
得到输出:
<a href="#">click me</a>
4)属性引用符号
我们都知道HTML是一个很不严格的标记语言(它的反面代表是XML),属性值可以不用引号,或者使用单引号、双引号、反单引号(仅IE浏览器支持)进行引用。如:
<a href=javascript:alert(1)-html>click me</a>
这样导致我们的闭合机制需要更灵活,以更大地提高检出率。因为如果同时提交'、"和这三种引号进行闭合,可能会因为网站SQL注入防御屏蔽了单引号导致请求失败,而目标输出又是双引号进行属性值引用的,这样就得不偿失了。所以,对于XSS漏洞挖掘工具来说,需要具备识别闭合引号的有无及其类型,并提交针对性的闭合payload。
3. 成为JavaScript代码的值
与“输出在on*事件内”的情况类似,有些JavaScript代码是服务端输出的,有时候会将用户提交的值作为JavaScript代码的一部分一起输出,如下场景:
<script>a="[输出]";...</script>
在这个场景中,我们的payload可以是:
</script><script>alert(1)//
这个是<script>标签的闭合机制,它会优先寻找最近的一个</script>闭合,无论这个</script>出现在哪里,都会导致这样的payload可以成功。
";alert(1)//
这个payload是直接闭合了a变量的值引用。
还有一个需要注意的场景:
alert(1)
又是这个神奇的payload,比如,恰好a变量在其他地方被eval等直接执行了。
前面曾提到如果//(JavaScript注释符)被过滤,还可以使用逻辑与算术运算符来代替。
4. 成为CSS代码的值
与“输出在style属性内”的情况类似,没什么特殊性,因此不做过多介绍。
6.1.3 请求中的玄机
前面针对输入与输出情况进行了分析,在XSS漏洞挖掘工具的请求机制中,我们也可以做很多优化。比如,具有针对性的payload就是一种避免冗余请求的方式。
还有一种思路叫做“探子请求”。在真正的payload攻击请求之前,总会发起一次无危害(不包含任何特殊符号)的请求,这个请求就像“探子”一样,来无影去无踪,不会被网站的过滤机制发现,就像是一次正常的请求。“探子”的目的有以下两个:
· 目标参数值是否会出现在响应上,如果不出现,就完全没必要进行后续的payload请求与分析,因为这些payload请求与分析可能会进行多次,浪费请求资源。
· 目标参数值出现在HTML的哪个部分,从上面的分析我们已经知道,不同的HTML部分对待XSS的机制是不一样的,请求的pay-load当然也不一样。
那么这个“探子”是以什么形式出现的呢?一般是26个字母+10个数字组合后,取8位左右的随机字符串,保证在响应的HTML中不会与已有的字符串冲突就行。知道探子的结构后,有利于我们对“探子”进行定位,尤其是对于输入点有多组参数值时,可以大大提高挖掘的效率。
6.1.4 关于存储型XSS挖掘
根据上一节对“反射型XSS挖掘”的各种玄机进行剖析,其实存储型XSS挖掘也就差不多了,只不过这里一般是表单的提交,然后进入服务端存储中,最终会在某个页面上输出。这个过程令大家头疼的莫过于这个“输出”。到底在哪里输出呢?有以下几种情况:
· 表单提交后跳转到的页面有可能是输出点。
· 表单所在的页面有可能就是输出点。
· 表单提交后不见了,然后就要整个网站去找目标输出点,这个需要爬虫对网站进行再次爬取分析,当然这个过程是可以优化的,比如,使用页面缓存技术,判断目标页面是否变动,一般发送Last-Modified与Etag头部,根据响应状态码进行判断即可。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论