【前端安全】聊聊 HTML 闭合优先级和浏览器解析顺序
【前端安全】聊聊浏览器解析顺序和 HTML 闭合优先级
最近在研究 XSS 的时候,发现一个特别容易被忽略的问题 —— 浏览器到底是怎么解析 HTML 的?为什么有些 payload 成功了,有些却怎么试都不行?其实这跟标签的闭合优先级还有解析顺序有很大关系。
这篇文章就来聊聊这个问题,顺便整理一下我踩过的坑和总结的规律。
一、浏览器是怎么解析 HTML 的?
我们都知道浏览器是按顺序从上往下解析 HTML 的,但有一点容易忽略:
只要遇到 <script>
,浏览器就会暂停 DOM 的解析,优先执行里面的 JS。
也就是说,DOM 结构和 JS 是交替解析的,而不是一起解析的。
举个栗子:
<p>前面的标签</p>
<script>alert('中间插入了一段 JS');
</script>
<p>后面的标签</p>
在 alert()
执行完之前,后面的 <p>
标签都不会被解析进 DOM 树里。
二、HTML 和 JS 中的“编码解析”
HTML 中能解析的编码
HTML 中的属性值,比如 <img>
的 src
、onerror
,是可以解析一些编码的:
比如:
<img src=x onerror=alert(1)>
a
实际上是字符 a
,所以这里最终会执行 alert(1)
。
更复杂一点的:
<img src="1"onerror=\u0061\u006c\u0065\u0072\u0074('\u0031')>
这是一堆 Unicode 转义,浏览器会还原成 JS 代码。虽然看着很花,但其实本质还是在执行 alert('1')
。
JS 里的编码也能玩花样
<script>
\u0061lert("<HelloWorld>");
</script>
\u0061
是 a
,所以这行代码其实就是 alert("<HelloWorld>")
。
这些“编码绕过”技巧在 XSS 中经常用到,尤其是某些过滤器只过滤了关键词,但没处理 Unicode 或 HTML 实体的时候,简直不要太好用。
三、结构性字符不可用编码绕过
有些结构性字符,是不能轻易编码的,否则浏览器会把它当成“普通值”,根本不当回事。
字符 | 作用 |
---|---|
" | 属性值起始/结束 |
' | 属性值起始/结束 |
= | 属性赋值符号 |
< | 标签起始 |
> | 标签结束 |
/ | 结束标签的斜杠 |
空格 | 属性之间的分隔 |
比如下面这个例子就失败了:
<img src="" onerror=alert(1) "">
你以为你写了 onerror=alert(1)
,但浏览器根本不认,它只会当成一个超长的 src
值。
四、比双引号闭合优先级更高的标签
有一类标签,你一旦打开,里面写啥都不会被解析成 HTML 标签或属性,直到你显式地把它关闭。
这些标签包括:
<!--
<iframe>
<noframes>
<noscript>
<script>
<style>
<textarea>
<title>
<xmp>
举个真实的 payload:
<script>var a="</script><script>alert(1);var a=""</script>
在第一个 <script>
中的字符串没闭合,导致后面的 </script>
被当成字符串的一部分吃掉了,浏览器继续往下读,直到遇到下一个 <script>
标签,再继续执行。于是 alert(1)
就偷偷溜进去了。
这种技巧经常被用来构造逃逸型 XSS,非常常见。
附一个好用的短xss payload网站
https://tinyxss.terjanq.me/