web前端安全基础与***(续篇)


继续前面的内容,由于转发到一些论坛时,限制了每个帖子中,添加图片链接的数量,而前篇包含的图片数量已经达到上限,因此开一个新帖继续讨论。

还记得在前篇中有一段针对使用 IE 浏览器用户的***载荷,如下所示 :

<script>function XSS(){
        a = new ActiveXObject('Microsoft.XMLHTTP');
        a.open('get', 'http://www.baidu.com', false);
        a.send();
        b = a.responseText;
        document.write(b);
        }
        XSS();
</script>

上面代码中,使用 IE 实现的XHR(XMLHttpRequest)对象访问百度首页并且通过javascript读取返回的数据(responseText),并且可以写入当前页面。

这是因为 IE 的同源策略没有很好地对XMLHttpRequest进行约束,例如,上述代码可能位于本地硬盘的一个HTML文本中(C:\Users\shayi\Desktop\XssPayloadTest.html

我们已经在上一篇博文中看到,IE 默认允许XHR跨域加载并读写资源;对于其它浏览器(FireFox 与 chrome)而言,本地文件系统路径与“百度首页”是不同源的,因此它们会限制当前HTML文本所在源中的javascript读写从百度首页返回的数据,换言之,这两个浏览器的同源策略默认仅允许XHR加载,读写相同源中的数据,除非对浏览器以及目标站点的web服务器配置为启用HTML5规范中引入的“跨域资源共享”(CORS)。

下面的示例代码,通过使用非 IE 浏览器支持的XHR对象,尝试跨域加载资源并写入至当前页面DOM中的一个节点:

(引用自《白帽子讲web安全一书》,略作修改)


<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="Content-type" content="text/html;charset=utf-8" />
        <title>XssPayloadTest</title>
        <script type = "text/javascript">
        var xmlhttp;
        function LoadXMLDoc(url)
        {
            xmlhttp = null;
            if (window.XMLHttpRequest)
            {
                xmlhttp = new XMLHttpRequest();
            }
            else if (window.ActiveXObject)
            {
                xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
            }
            if (xmlhttp != null)
            {
                xmlhttp.onreadystatechange = state_Change();
                xmlhttp.open("GET", url, true);
                xmlhttp.send(null);
                
            }
            else
            {
                alert("your browser does not support XMLHTTP");
            }
        }

        function state_Change()
        {
            if (xmlhttp.readyState == 4)
            {
                if (xmlhttp.status == 200)
                {
                    document.getElementById('T1').innerHTML = xmlhttp.responseText;
                }
                else
                {
                    alert("problem retrieving data:" + xmlhttp.statusText);
                }
    
            }
        }        
        </script>
    
    </head>
    <body onload = "LoadXMLDoc('http://bbs.pediy.com/index.php')">
    <div id = "T1" style = "border:1px solid black;height:40;width:300;padding:5"></div><br />
    <button onclick = "LoadXMLDoc('http://shayi1983.blog.51cto.com')">Click</button>
    </body>
</html>


这个在HTML文档头部(head元素内)引入的javascript定义了两个函数: LoadXMLDoc()通过浏览器支持的XHR类型来跨域发起HTTP GET请求并加载资源;

 state_Change()检查对方web服务器返回的HTTP响应状态码,然后决定是读取响应数据的内容并写入当前页面(状态码为200);还是给出服务器端返回的错误信息(除200以外的其它状态码)。

在HTML文档体(body元素内),通过实际调用LoadXMLDoc()来对看雪论坛首页发起跨域请求(注意,当前“源”是本地文件系统上的测试用HTML页面),然后尝试将对方返回的“响应文本”(responseText)写入当前页面DOM的T1节点的内部HTML文本中,并且在文档体中添加一个按钮,用户点击时将再次通过XHR对象,发起对我的51cto博客页面的跨域请求。

上面代码在FireFox中的测试结果如下所示:


wKiom1Uc73OSImYGABB1Fgm5ODg544.jpg


最后要指出一点,浏览器一般通过URL中的协议,主机名与端口号来判断一个页面文档所属的域,而在一个新打开的页面的URL中,只要3者之一与前面那个页面的URL不同,都会被浏览器的同源策略认定为属于不同域,从而阻止新开启页面上的脚本通过前者页面的URL来发起跨域请求,而符合HTML5 CORS 规范的XHR 发起的跨域请求则不在此限,当然前提是需要经过目标域(即要“跨”的域)中的服务器许可,如果目标域的web服务器在返回的 Access-Control-Allow-Origin 响应头中指定的值与发起跨域XHR所属的源始域相同,那么浏览器将允许这个XHR跨域请求。

为了更清楚说明XHR询问是否可以CORS的场景,假设一个HTML页面所属域为

http://www.webSite.com(即该页面通过这个URL加载),在该页面中,有一段生成跨域HXR的  javascript 代码如下:


var xhr = new XMLHttpRequest();
xhr.open("PUT", "http://www.friendlySite.com/index.html", true);
xhr.send();


xhr.open() 的第三个参数为 true,表明请求为异步发送,这是默认值,有些浏览器为了性能上的考量,甚至拒绝将该参数设置为 false(同步),如果你这么做了, Firebug 将会给出类似下面的提示:


wKiom1VAZwGQ4vWSAANaNvFhUKo659.jpg


遇到 xhr.send() 语句时,浏览器会向 www.friendlySite.com  发起一个HTTP 请求,用于“证实”www.friendlySite.com 是否愿意与 http://www.webSite.com 共享资源(通过后者打开的页面文档),请求头如下:


OPTIONS http://www.friendlySite.com/index.html HTTP/1.1
Host: www.friendlySite.com 
User-Agent: Mozilla/5.0(Macintosh; Intel Mac OS X 10.7; rv:11.0) Gecko/20150101 Firefox/37.0
Accept: text/html,application/xhtml+xml,applicatiin/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.5
Origin: http://www.webSite.com 
Accept-Control-Request-Method: PUT


以上HTTP请求(这个包含 CORS 请求头的 HTTP 请求又称为“Preflight”请求)中,注意 OriginAccept-Control-Request-Method 这2个头部,源始文档所属域为 www.webSite.com  ,因此 Origin 头部就是这个值;源始文档中的XHR 对象请求的方法为 PUT,因此 Accept-Control-Request-Method 头部就是该值。还要注意浏览器使用的是 HTTP OPTIONS 方法,该方法在最初的HTTP协议规范中,用于查询web服务器支持的 HTTP 方法类型,现在则演变为用于查询对方的 CORS“许可策略”。

如果目标域 www.friendly.com  允许 CORS ,其返回的HTTP 响应头应该如下所示:


HTTP/1.1 200 OK
Date: Wed, 13 Apr 2015 06:51:53 GMT
Server: Tengine
Access-Control-Allow-Origin: http://www.webSite.com
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 10
Content-Length: 0


以上HTTP响应中,Access-Control-Allow-Origin 响应头的值与发出跨域请求的XHR 对象所在页面文档所属的域是同一个,表明目标域允许与 www.webSite.com

的 CORS ,如此一来,浏览器将允许 www.webSite.com 域页面中的XHR对象和后续的 javascript 代码读取,操纵和显示来自 www.friendly.com 域返回的数据,例如读写 xhr.responseText 的内容时,才不会发生错误。

另外,Access-Control-Max-Age 响应头的含义为,该响应的有效期,以秒为单位,例如其值为 3600 时,浏览器在一小时内对目标域的每次跨域访问,都不需要使用 Preflight 请求,这就减少在请求头中增加多余的头部造成的客户端与服务器端处理负担,以及链路带宽的浪费(每次少传输几十到上百字节总是好的);但是一小时后,浏览器必须再次发送 Preflight 请求至服务器,以更新目标域的 CORS 许可策略,一般而言,目标域为了其自身数据的安全考量,Access-Control-Max-Age 的值不会设的太长,但是像上例中的10秒那么短也不太实际,各位可以自行访问其它互联网上的站点,通过工具来测试这个值

Access-Control-Allow-Credentials 请求头的值如果为 true,那么 www.webSite.com 域页面中的 XHR 对象以及 javascript 脚本代码,将能够读写

目标域 www.friendly.com 设置的 cookie,HTTP 基本认证字串(base-64 格式的用户名与密码,即目标域赋予的用户会话令牌),摘要认证字串(MD5 或其它摘要算法加密的用户名与密码),以及由目标域 www.friendly.com 设置的客户端 SSL 证书(假设目标域是电子商务网站或网上银行,需要验证企业用户的身份,就有可能用到客户端 SSL 证书),以上这些证书和认证信息,以及 cookie 形式的会话令牌,都将能够被 www.webSite.com 域页面中 XHR 对象以及 javascript 脚本代码读写访问;一般而言,如果目标域源始域之间没有很好的友情和信任关系,Access-Control-Allow-Credentials 响应头的值应该为 false 会比较安全。 

(作为对比,前面的本地文件系统上的测试HTML页面中的 javascript 代码无法将来自看雪以及51cto域的 xhr.responseText 写入至测试页面,就是因为浏览器没有收到来自看雪以及51cto域允许与“本地域”进行CORS的响应头。)

前面提到,当没有启用 CORS 时,浏览器的同源策略禁止 XHR 对象跨域读写资源;但是 iframe 标签则不受浏览器的同源策略约束,可以随意跨域加载资源,细节容后作介绍,这里先来看看一个 iframe 标签的效果:


<!DOCTYPE html>
<html>
    <head>
    <meta http-equiv="Content-type" content="text/html;charset=UTF-7" />
        <title>XssPayloadTest</title>
    </head>
    <body>
        <iframe src="http://www.baidu.com/">本页面必须在支持iframe的浏览器中访问</iframe>
    </body>
</html>


上面文档是为了兼容早期不支持 iframe 标签的浏览器量身定做的:具体而言,不支持 iframe 的浏览器在碰到这个标签的时候,将无法跨域加载页面资源,而是显示标签内部的数据,就本例而言,会提示用户需要使用支持 iframe 的浏览器才能正常显示内容;相反,当前的浏览器多数都支持 iframe 标签了,使用它们打开这个文档,就不会显示提示信息,以 FireFox 为例:


wKiom1U55HKg6l1OAAvP7vbdKD4834.jpg



wKiom1U559jRCoHyAAaXh2kZjwU615.jpg


wKiom1U56TOCFfkRAAO8wzVoTQw648.jpg


wKiom1U57paQSJQiAA2IDcbu6wg014.jpg


wKiom1U59QKDjzeDAA36VfQsU2A631.jpg


通常以大写的 X 开头的头部(包括请求与响应头)都是 HTTP 规范中的“扩展”头部;

上图中的 X-XSS-Protection 响应头是用于告诉客户端浏览器开启内置的 XSS 过滤机制(其值为 1 时,表示开启),只要浏览器实现了标准中定义的 X-XSS-Protection 规范,那么应该都能识别这个响应头(同时还需要目标站点的 web 服务器能够意识到传递这个响应头对于客户端安全的重要性,作为对比,百度的 web 服务器就不会传递这个响应头)

X-XSS-Protection 起到的防御效果是有限的,仅能阻止反射型 XSS,也就是当***载荷出现在 URL 中提交至服务器时,如果服务器没有过滤并且直接在响应中将***载荷“反射”回客户端,那么得益于先前或本次响应设置的 X-XSS-Protection 头部,浏览器将检查并过滤危害字符;X-XSS-Protection 无法防御存储型 XSS。


上面一系列图片中出现的 X-Frame-Options 已经介绍过,主要是用来防止***者通过 iframe 跨域嵌入页面内容,构造出点击劫持(ClickJacking)的伪装 UI ,欺骗用户点击。如果将 X-Frame-Options 头部的值设为 DENY ,那么任何页面都不能通过 iframe 嵌入其响应体中的 HTML 文档(如上所述,实际上设置了 X-Frame-Options 头部的服务器不会在响应体中跨域发送 HTML 文档),即便是同源(相同域)的页面也不行。


由此可见,google 的安全策略还是做的比较完善的,毕竟是世界领先的互联网云计算,云存储公司,以及信息检索服务提供商


《有效阻止各种类型XSS的CSP(内容安全策略)》

以 facebook 服务器为例,当浏览器向其请求一个页面文档时,支持 CSP 的服务器在携带该文档的 HTTP 响应头部会添加一个  content-security-policy 响应头,支持 CSP 的浏览器在解析,渲染该文档之前,必须先读取该头部的值,以了解服务器对该页面资源设置的客户端安全加载策略,这样,即便页面中包含***载荷,也会因为该***载荷没有从 CSP 允许的域加载资源,而使***失败。另外,XSS ***通常无法注入,修改响应头部,因此该字段可以被用于正确判断服务器的本意:


 content-security-policy: default-src *;
script-src https://*.facebook.com http://*.facebook.com https://*.fbcdn.net http://*.fbcdn.net *.facebook.net *.google-analytics.com *.virtualearth.net *.google.com 127.0.0.1:* 

*.spotilocal.com:* 'unsafe-inline' 'unsafe-eval' https://*.akamaihd.net http://*.akamaihd.net *.atlassolutions.com chrome-extension://lifbcibllhkdhoafpjfnlhfpfgnpldfl;
style-src * 'unsafe-inline';
connect-src https://*.facebook.com http://*.facebook.com https://*.fbcdn.net http://*.fbcdn.net *.facebook.net *.spotilocal.com:* https://*.akamaihd.net wss://*.facebook.com:* 

ws://*.facebook.com:* http://*.akamaihd.net https://fb.scanandcleanlocal.com:* *.atlassolutions.com http://p_w_upload.fbsbx.com https://p_w_upload.fbsbx.com;


对于上面的 CSP ,浏览器将会作出如下解释,并用其作为判断是否加载页面资源的标准:

1。 default-src * 表示允许从任何域加载任何资源,在这条指令中引入通配符星号是相当危险的,虽然该指令的优先级最低(策略冲突时,以其它指令为准,而不是 default-src),但一般也不推荐用通配符,这里应该是 facebook 前端开发工程师犯的低级安全错误;

2。script-src 指令表示允许从哪些域加载脚本文件,这里允许的域有:

     https://*.facebook.com

     http://*.facebook.com

     https://*.fbcdn.net

     http://*.fbcdn.net

     *.facebook.net(估计租下了 .net 通用顶级域下的这个子域,才敢如此设置)

     *.google-analytics.com

     *.virtualearth.net

     *.google.com(google 一向是web安全的模范站点)

     127.0.0.1:*(这是多余的吧?)

     *.spotilocal.com:*(允许该域服务器上的任何端口。。。可以对目标进行端口扫描了)

     'unsafe-inline'

(表示允许该页面中内嵌的脚本,即通过 script 标签引入的,由于***者也可以注入 script 标签,这就说明 facebook 有一套良好的 XSS 过滤机制,才敢使用 unsafe-inline 这个参数;另外,如果没有显式指定这个参数,则默认禁止内嵌脚本,但这就无法执行“善良的”script 标签内代码)

     'unsafe-eval'

(表示允许执行 eval() 家族的函数,后者通常用于在“严峻”的已过滤环境中执行恶意脚本,这表明 facebook 对自己的 XSS 防火墙很有自信)

      https://*.akamaihd.net

      http://*.akamaihd.net

      *.atlassolutions.com


3。style-src * 同样表示允许从任何域加载样式表文件(危险!);

      'unsafe-inline' 表示允许内嵌样式,即通过 style 标签引入的;

4。connect-src 表示允许通过 XHR 对象向哪些域发起跨域请求并读写响应,这里

    允许“跨”的域有:

    https://*.facebook.com

    http://*.facebook.com

    https://*.fbcdn.net

    http://*.fbcdn.net

    *.facebook.net

    *.spotilocal.com:*

    https://*.akamaihd.net

    http://*.akamaihd.net

    wss://*.facebook.com:*

    ws://*.facebook.com:*

    https://fb.scanandcleanlocal.com:*

    *.atlassolutions.com

    http://p_w_upload.fbsbx.com

    https://p_w_upload.fbsbx.com      


对于任何其它站点服务器返回的 CSP 策略,都可以按照类似上述方法来解释,推测出浏览器最终将只允许哪些内容。
     


《关于HTML文档的两种解析模式:HTML 与 XHTML 之间的差别》


浏览器根据HTML文档起始处的“<!DOCTYPE xxxx>”来判断应该使用何种解析模式,当xxxx的值为“html”时,表示为HTML解析模式,该模式对文档的语法要求比较宽松,浏览器的HTML解析引擎会容许并且自动修正绝大多数的语法错误,例如下面这个例子:


<!DOCTYPE html>
<htMl lang="en">
    <head>
        <meta http-equiv="Content-type" content="text/html;charset=utf-8" />
        <title>XssPayloadTest</title>
    </head>
    
    <body>
    ' " `
    <a href = http://www.baidu.com>百度首页。。。
    </oops>
    <IMG src = #>
</hTml>


可以看到,上述文档的HTML标签大小写混用;body元素与a元素没有闭合;

a元素的href属性的值(百度首页的URL)没有用双引号包含;存在一个没有起始(标签)的oops元素;img元素使用全大写而且没有闭合。。。。这些语法错误都可以在浏览器中得到修正,前提是使用HTML解析模式,以 IE 为例,打开上述文档,IE 会在其进程地址空间中构建DOM节点时,修正错误,缺少的元素:


wKiom1Uik7_Ck8F6AASIkTL_xkE767.jpg


wKioL1Uil-vidE2lAAW8FCWTpOg903.jpg


wKiom1Uinnej1ZCJAArrc8PBDkI579.jpg


另一方面,XHTML(衍生自XML)解析模式则非常严格,文档中存在上述任意一种类型的错误都会导致浏览器无法正常显示该文档,或者给出错误提示。

再者,采用HTML解析模式,遇到“<script>”,“<style>”,“<textarea>”,“<xmp>”等元素标签,都会致使浏览器切换到特殊解析模式,例如碰到script标签,调用javascript解析引擎;碰到style标签调用CSS解析器等等。

而在XML类文档中(包括XHTML),这些标签内部还需要嵌套一个前后不对称格式的数据标签,如下所示:


<script type="text/javascript">
<![CDATA[
 //将此处注释掉的内容改成javascript代码,其它类型的特殊解析模式标签,以此类推
]]>
</script>


上例中,“<![CDATA[”“]]>”字符串标签之间才是真正添加javascript代码的位置。

例如,BurpSuite Proxy 模块拦截到的从“博文视点”站点首页,返回的HTTP响应体的HTML文档中,就包含了对“<![CDATA[]]>”的使用:


wKiom1Uis9KgqqKMAAncRvwj20s116.jpg


HTML 与 XHTML 解析模式差异总结:

使用XHTML解析时,仅遇到 script 或 style 标签,还无法切换进入到对应的解析模式(调用 javascript 或 css 解析引擎),而是必须添加“<![CDATA[....]]>”数据标签,并且将脚本代码或样式表放在其中以实心句点表示的位置处,才能正常工作。




对于下面这个没有按照规范编写的img元素:


“<img src= "http://www.baidu.com"  title=""οnerrοr="alert('xss')" class=examples>”


一共有4个属性:src,title,onerror,class。其中class属性的值没有使用双引号包含,三种浏览器各自使用不同的方式解析这个img元素:


wKiom1Ujfl-gjVfdAAOLtOiJdAE909.jpg


wKiom1UjgFnQnUtgAANp8YFtUNQ840.jpg


wKiom1UjgoTCH4IAAAR2gnUYQ8M359.jpg


从上面可以看出,只要是元素属性的值,无论是哪一种浏览器,在解析的时候都会自动向其添加双引号。另外,onerror 属性的值为一个javascript语句,运行结果是弹出提示框。

如果将包含javascript语句的双引号改成“反引号”(`),则三种浏览器在解析时,都会自动在反引号外侧再添加双引号,换言之,无论img元素属性的值为何,总是使用双引号包含,而这会导致添加反引号的javascript代码无法执行


wKioL1UjilnC4QAsAAO5ZSWx5xA117.jpg

这里还有一个很重要的知识点:一般而言,浏览器在解析img元素的任意属性值时,无论原始文档中是否将属性值用双引号包含,浏览器都会自动将其包含。

但是,如果属性的值为以左尖括号开始的元素,例如script,并且原始文档中没有将其用双引号包含,那么浏览器在解析的时候会产生混乱(对于三大浏览器而言都一样),虽然这样并不会导致执行script标签中的javascript代码,但是浏览器会给出一些错误提示(IE),或者会干扰到对img元素其它属性值的正常解析(FireFox 与 chrome),例如原始文档中的代码:


<img src = "#" title = <script>alert('xss in value of element properties');</script> class = examples>


其中title的属性值为以左尖括号起始的script标签,没有使用双引号包含,那么浏览器们会如何解析呢:


wKioL1Uj86bzxMpPAARIwBAMKRI157.jpg


wKioL1Uj-y6CEYu0AAb56FKRdGA263.jpg

wKiom1Uj_9zg7YNkAAG6O2LVC0M776.jpg


wKiom1UkCuyTuP_HAAPZhJkHIsw198.jpg


从上面的解析结果还可以发现一个事实,那就是浏览器在解析时会自动忽略(不处理)分隔属性与属性值的等号(=)两侧的空格字符。在原始文档中,img元素的title属性后先接上一个空格符,然后依序是等号,第二个空格符,左尖括号。。。。而在浏览器解析时忽略了两侧的等号。

(img元素与第一个属性之间的空格符不会被省略,而且是必须的)


wKiom1UkhQGh5ApvAAM-88twvMo165.jpg


《浏览器如何解析HTML文档中,错误的元素标签嵌套》


以下面代码为例,其在body元素内部存在错误的标签嵌套:


<!DOCTYPE html>
<html>
    <head>
    <meta http-equiv="Content-type" content="text/html;charset=utf-8" />
        <title>XssPayloadTest</title>
    </head>
    <body>
        <i <b>xss
    </body>
</html>


wKioL1UkjPuTmUhNAAK8ROjztkU962.jpg
wKiom1UkjS_xp0DNAAHwWuahQjs258.jpg


wKioL1UkkDShPP2jAALNw-PukPI575.jpg


通过前面的例子可知,浏览器不支持在元素属性名中使用特殊字符,那么,浏览器是否支持在元素名称中使用特殊字符,例如左尖括号以及等号呢?考虑下面这段代码:


<!DOCTYPE html>
<html>
    <head>
    <meta http-equiv="Content-type" content="text/html;charset=utf-8" />
        <title>XssPayloadTest</title>
    </head>
    <body>
        <i<"<b>">xss
    </body>
</html>


在body元素内部,试图将第二个左尖括号开始的字符串作为 i 元素名称的一部分,

来看看浏览器们会如何解析这个文档:


wKiom1UkqYyh1qXeAAQ6LEaEyJc397.jpg


wKioL1UkqvqyGBlTAAYZr086AVw386.jpg


IE 中的情况与上述两者类似,这里就不截图说明了。大家可以自行尝试向 i 元素名称中添加等号(=),浏览器也会将其当成是元素名称的组成字符,从某种意义上而言,这种解析上的缺陷不得不视为当前版本的一个安全隐患

在chrome的解析截图中,看到了将特殊字符进行HTML实体编码的用法,其实,

浏览器的HTML解析引擎应该都能识别在HTML文档文本节点元素属性值内,以HTML实体编码的字符序列,并将其解码,还原成可打印的ASCII字符。

这就是chrome将文本节点中的左尖括号编码的原因——它可以解码成明文字符。

这里隐含了一条重要的安全规则,浏览器在解析时应该遵守,否则就有可能在客户端产生xss漏洞,假设经由服务器端返回的HTML文档中,已经对文本节点与元素属性值进行了HTML实体编码(或者10进制,16进制编码),意味着这些被编码的字符是具有危害性的(一般而言,普通字符不需要编码)

那么浏览器就不能将解码后的危害性字符序列(如“<script>”)当成指令执行,只能作为数据或字符串显示,或者索性不解码,在HTML文档中显示编码字符。

因为服务器端的xss过滤器已经意识到,危害性字符出现在文本节点与元素属性值中,可能导致xss漏洞,因此将其编码,而浏览器能做的就是,仅显示解码后的内容,而不是执行,或者保持原样输出。

为了更好地说明其中的微妙之处,来看一看下面的几个实例


<!DOCTYPE html>
<html>
    <head>
    <meta http-equiv="Content-type" content="text/html;charset=utf-8" />
        <title>XssPayloadTest</title>
    </head>
    <body>&rarr;&#128569;</body>
</html>


这个例子在body元素内部的文本节点中分别插入了HTML实体编码的“&rarr;”,解码后的效果是一个指向右边的 unicode 箭头;以及HTML 16进制编码的“&#128569;”,解码后的效果是一个表示成喜极而泣的猫脸的 unicode 字符。

因为这2个字符都是非危险性的,因此三大浏览器都能将其解码后显示:


wKiom1UlQNqxiuq3AAJKYMABAEA107.jpg


wKiom1UlQeThkMLfAAGfTB1G808125.jpg


令人诧异的是,以兼容性与安全性,国际化著称的chrome浏览器,不能识别以HTML 16进制编码的喜极而泣猫脸字符,因此这里就不发图了,各位可以自行验证。

第二个例子指出,浏览器可以识别并解码在元素属性值中,以HTML 16进制编码的非危害性字符,尽管后者通常是不需要编码的:


<!DOCTYPE html>
<html>
    <head>
    <meta http-equiv="Content-type" content="text/html;charset=utf-8" />
        <title>XssPayloadTest</title>
    </head>
    <body><img src="ht&#x74;p&#x3a;/&#x2f;ucenter.51cto.com/avatar.php?uid=4681835&size=middle"></body>
</html>


img元素的src属性的值中,“&#x74;”是HTML 16进制编码后的 ASCII 字符“t”;“&#x3a;”是采用相同编码格式的 ASCII 字符“:”;“&#x2f;”是 ASCII 字符“/

因此浏览器解码后应该能识别src的属性值为“http://ucenter.51cto.com/avatar.php?uid=4681835&size=middle”

这是我的头像的URL,用浏览器打开上述文档,如果能正确加载头像,说明解码成功:


wKiom1UlUJSBuOCgAATi_DHj1sE380.jpg


然而,浏览器无法识别以及解码出现在属性名称中的HTML编码字符,(这个限制还包括了对属性名称赋值的等号

以上面的例子而言,这会造成浏览器无法加载头像,其中,等号使用了HTML 16进制编码:


<!DOCTYPE html>
<html>
    <head>
    <meta http-equiv="Content-type" content="text/html;charset=utf-8" />
        <title>XssPayloadTest</title>
    </head>
    <body><img src&#x3d;"http://ucenter.51cto.com/avatar.php?uid=4681835&size=middle"></body>
</html>


以及在属性名称中出现的HTML 编码字符:


<!DOCTYPE html>
<html>
    <head>
    <meta http-equiv="Content-type" content="text/html;charset=utf-8" />
        <title>XssPayloadTest</title>
    </head>
    <body><img s&#x72;c="http://ucenter.51cto.com/avatar.php?uid=4681835&size=middle"></body>
</html>


各位可以自行测试。


《页面使用 UTF-7 字符编码导致的 XSS 漏洞详解》

很多安全类书籍都介绍到 UTF-7 字符编码会导致 XSS 漏洞但是部分内容各执一词,且描述不清楚,这里总结如下:

UTF-7 编码是 unicode 编码的一类子集,后者包含的其它编码类型有 UTF-8,UTF-16LE(小端法,将代表编码字符的最低有效字节放在前面),UTF-16BE(大端法,将代表编码字符的最高有效字节放在前面),UTF-32LE,UTF-32BE 等等。在 UTF-7 编码方案中,左尖括号被编码成“+ADW-”;右尖括号被编码成“+AD4-”,其中的加号与减号分别标识着 UTF-7 编码序列的开始与结束;如果服务器端的 XSS 过滤器没有过滤这两个UTF-7 编码格式的左右尖括号,(或没有将其编码成其它无害形式的字符),并且客户端浏览器的版本过于老旧(例如 IE 7),那么当被注入***载荷的页面返回至客户端时,浏览器将解码 script 标签前后的 +ADW- +AD4- 字符,还原成明文的左右尖括号,从而执行 script 标签内部的恶意代码:


<!DOCTYPE html>
<html>
    <head>
    <meta http-equiv="Content-type" content="text/html;charset=UTF-7" />
        <title>XssPayloadTest</title>
    </head>
    <body>
        +ADW-script+AD4-alert("xss")+ADW-/script+AD4-
    </body>
</html>


以上文档中,web 服务器(愚蠢到)在 meta 元素中显式指定字符集为 UTF-7,如果浏览器同样愚蠢(例如 IE 7以下版本,美其名曰是为了保持向后兼容)遵循这个设置字符集的指令,就会解码任何 UTF-7 字符,最终导致弹出提示框。需要补充说明的是,多数现代浏览器均已经不支持 UTF-7 了,因而上述文档在最新版本的三大浏览器中测试,都不会弹出提示框;另外,实际的触发环境和条件更为复杂,考虑如下场景中,假设用户的浏览器是 IE 6/7 :

1。如果 web 服务器在返回的 HTTP Content-Type 响应头中,没有明确设置字符集编码,并且响应体中的 HTML 文档中的 meta 元素内,也没有设置字符集编码,那么浏览器在碰到任何 UTF-7 字符时,都会尝试确定其编码方案,并且解码还原成明文字符,以上述文档为例,就会弹出提示框;

2。如果 web 服务器在返回的 HTTP Content-Type 响应头中,明确设置字符集编码为 UTF-8,并且响应体中的 HTML 文档中的 meta 元素内设置的字符编码为 UTF-7,那么浏览器最终将按照响应头中的设置(响应头的优先级比响应体中 meta 标签的优先级高 ),以上述文档为例,会弹出提示框(因为采用 UTF-8 字符编码时,浏览器无法识别,解码 +ADW- +AD4- 字符);

3。如果 web 服务器在返回的 HTTP Content-Type 响应头中,设置字符集编码为 UTF-7,并且响应体中的 HTML 文档中的 meta 元素内设置的字符编码为 UTF-8,

根据浏览器的采用优先级原则,上述文档将会弹出提示框;

总结,应对 UTF-7 XSS***,从最终用户防御的角度来看,应该确保浏览器总是处在当前的最新版本状态;从 web 站点防御的角度来看,应该明确在响应头与响应体的 HTML 文档中,设置字符编码为 UTF-8 或者其它安全的编码方案;

对于每个包含HTML内容的响应,web应用需要在其中包含 Content-type 头部,并且用“charset=”指令设置一个标准的,(浏览器)可辨识的字符集,
例如:

Content-Type: text/html; charset=utf-8

或者: 
Content-Type: text/html; charset=ISO-8859-1
(如前所述,还要确保响应中的所有可能位置指定了相同的字符集)

并且过滤掉任何 UTF-7 危险字符,如果没有把握通过编程屏蔽所有危害字符,则可以模仿浏览器的 HTML 解析引擎将用户输入的内容在内存中保留一份副本,将其渲染成 HTML 页面,在渲染结果中查找任何明文的危害字符,对其进行编码过滤,然后再次渲染,再过滤,直到完全清除干净后,才可以把最终的文档返回给客户端。

(这也是许多优秀的服务器端 XSS 过滤器,WAF 采用的工作模式)


《存储型 XSS 示例》

一些社交网站如 twitter 提供的站内短信功能,在其中会通过 a 标签的 href 属性值进行短信链接的导航,这个属性值是用户能够控制的输入数据,所以 twitter 对其进行了严格输入检查,编码,过滤等操作,主要是过滤尖括号以及 script,embed,object 等危害性元素。

假设用于生成短信链接的输入框的初始 HTML 代码如下:


<a href=""></a>


在上面场景中,***者想方设法要绕过 twitter 服务器的 XSS 过滤器,最终目的是让输入的***载荷存储在由服务器的 web 应用维护的,某个能够公开访问的“页面”(类似新浪微博的“广场”功能),其它用户访问这个页面时,XSS 漏洞在用户浏览器中触发,执行恶意脚本代码。由于上面提到的过滤规则,插入尖括号的方法已经不再适用,必须寻找新的方法来绕过,例如下面这个***载荷使用了众多web 前端***技术:

1。双引号元素属性闭合;

2。 onmouseover 事件结合 eval 函数执行任意代码;

3。对 URL 进行 UTF-16 编码绕过 XSS 防火墙;

4。利用社会工程学***来引诱用户点击链接;


http://t.co@" style="font-size:99px;" οnmοuseοver="eval(location.href='http:\u002f\u002fwww.baidu.com\u002f')" class/


其中,邮件符号 @ 后面的第一个双引号用于闭合原始 HTML 代码中, 等号后面的第一个双引号,于是***者可以在后面插入自定义内容;style属性将字体增大到 99像素,保证受害者能够“准确地”点击到具诱惑性的恶意链接; 属性代表的事件在用户将鼠标指针移动到该元素(a 元素)范围内时触发,事件触发后,执行自定义代码,将当前文档的 URL 重定向到百度首页(这里经过我净化,实际的***载荷中,其 URL 肯定是由***者控制的站点上的恶意 .js 文件);需要注意,考虑到 twitter 的 XSS 防火墙可能会过滤以“http://”起始的字符串,因此使用 UTF-16 编码(\u002f)对恶意 URL 中的正斜杠(/)进行转义,这样就能绕过服务器的 XSS 防火墙(只要不是精确匹配,输入字串就能通过检查),关键在于,转义后的字符序列在作为 HTTP 的响应体内容返回至客户端后,浏览器会解码,还原成正斜杠,导致最终访问该 URL ;另外, 属性的值必须是能够执行的函数或者代码快,这就是调用 eval 函数的目的;最后,使用 class 后面的正斜杠来注释掉原始 HTML 代码中的第二个双引号,避免浏览器因为语法错误而无法执行脚本。

补充说明一点,以 “\u”开始的6个 ASCII 字符,是 javascript 语言核心中,用于

unicode 字符的转义序列,“\u”后面的4个 ASCII 字符(或2个16进制数),可以采用 UTF-8/16/32 等编码形式;这里使用的是 UTF-16 编码(三种编码都是 unicode 编码的子集),使用这些编码是为了支持一些不能正常显示 unicode 字符的计算机系统。实际上,unicode 字符转义使用与 ASCII 字符的16进制编码相同的

映射;例如,对于字符“alert”,其 ASCII 16进制编码形为“\61\6c\65\72\74”;其 unicode UTF-16 编码转义形式为“\u0061\u006c\u0065\u0072\u0074” ,第2个16进制数与 ASCII 字符的16进制编码相同,第1个16进制数在编码 ASCII 字符时为 00;在编码其它 unicode 字符时不为 00。

javascript 语言核心(以及实现它的浏览器内部的 javascript 解释引擎)直接支持

unicode 字符转义序列,这意味着,只有这些转义字符出现在页面中与 javascript 上下文相关的环境中时,才能被浏览器正确解码,这些环境包括但不限于(其它上下文各位可以自行挖掘):

1。 script 元素内部;

2。 支持绑定事件处理程序的元素内部;

3。支持在属性值中使用 javascrip 伪协议的元素;

如果直接在 HTML 文档中(例如普通的元素内)使用 unicode 字符转义序列 ,浏览器可能无法识别并解码成相应的 unicode 字符,下面通过例子演示:


<!DOCTYPE html>
<html>
	<head>
        		<meta http-equiv="Content-type" content="text/html;charset=utf-8" />
        		<title>XssPayloadTest</title>
    	</head>
    	<body>
		\u0061\u006c\u0065\u0072\u0074
		<div>
			<a href = "javascript:\u0061\u006c\u0065\u0072\u0074('xss!')">百度首页</a>
			<input type = "text" name ="search" id ="search" value = ""onfocus = \u0061\u006c\u0065\u0072\u0074('利用事件处理程序的xss!')//" class = "text_area"/>
			<script>
				document.write("\u0061\u006c\u0065\u0072\u0074");
			</script>
			
	</body>
</html>


第8行直接在 body 元素(页面文档体)中写入ASCII 字符串“alert”的 unicode UTF-16 编码转义形式 “\u0061\u006c\u0065\u0072\u0074”,浏览器将不会解码并还原成“alert”,而是直接在页面输出\u0061\u006c\u0065\u0072\u0074”;

第10行在 a 元素的 href 属性值中使用 javascript 伪协议,其中的“alert”保留字使用 unicode UTF-16 编码转义形式,浏览器会解码并还原成 “alert”,导致在点击名为“百度首页”的超链接时,弹出显示“xss!”的信息框;

第11行给 input 元素的 onfocus (鼠标点击聚焦输入框)事件绑定的回调函数正是采用 unicode UTF-16 编码转义形式的 “alert”,浏览器会解码并还原,导致用户点击输入框时触发事件处理程序,弹出提示框;

第13行在 script 元素内调用 document.write(),作为其参数传递的正是 unicode UTF-16 编码转义形式的 “alert”,浏览器会解码并还原,最终在页面输出 alert 字符串。

下面的截图验证了浏览器的 javascript 解析引擎在上述三种 javascript 执行上下文中,遇到 unicode  字符转义序列时,所表现出的行为:


wKiom1Vi0DyyOX1OAAHdiu6KHR4126.jpg

这个小实验再次说明了基于黑名单过滤的 XSS 防火墙是不安全的:你也许想到要在元素属性值中过滤 alert ,script,javascript 等字符串,但是你能考虑周全地过滤掉所有相应字符串的 unicode 字符转义序列吗?黑名单匹配曝露的受***面是如此之广,以至于任何漏网之鱼都可能被***者用于发起 XSS !



为了增强链接的视觉诱惑性,我在 a 元素的 innerHTML 中,添加了名为“邀请码发放页面”的文本节点(请勿在论坛中尝试!),最终的注入效果(即存储到服务器上的漏洞页面)如下所示:


<!DOCTYPE html>
<html>
    <head>
    <meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
        <title>XssPayloadTest</title>
    </head>
    <body >
        <a href="http://t.co@" style="font-size:99px;" οnmοuseοver="eval(location.href='http:\u002f\u002fwww.baidu.com\u002f')" class/">邀请码发放页面</a>
    </body>
</html>


使用当前任意类型的最新版浏览器打开上面 HTML 文件,就会重定向到百度首页,这就完整的模拟出***载荷成功绕过,存储到服务器上,并且返回给客户端后,用户浏览器成功执行代码的场景,截图如下:


wKioL1U--JKhyHq3AAio6FDPfHM964.jpg