首发于 小t的成长纪录

HTML与JavaScript自解码机制

关于这个自解码机制,我们直接以一个例子(样例0)来进行说明:

<input type="button" id="exec_btn" value="exec" onclick="document.write ('<img src=@ onerror=alert(123) />')" />

我们假设 document.write 里的值是用户可控的输入,点击后, document.write 出现一段 img HTML onerror 里的 JavaScript 会执行。此时陷阱来了,我们现在提供一段 HtmlEncode 函数如下(样例A):

<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>
<input type="button" id="exec_btn" value="exec" onclick="document.write (HtmlEncode('<img src=@ onerror=alert(123) />'))" />

我们知道 HtmlEncode('<img src=@ onerror=alert(123) />') 后的结果是:

&lt;img src=@ onerror=alert(123) /&gt;

这个样例 A 点击后会执行 alert(123) 吗?下面这个呢(样例 B)?

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

在样例 A 和样例 B 中, document.write 的值似乎是一样的?实际结果是样例 A 点击不会执行 alert(123) ,而是在页面上完整地输出 <img src=@ onerror=alert(123) /> ,而样例 B 点击后会执行 alert(123)

我们要告诉大家的是,点击样例B时, document.write 的值实际上不再是:

&lt;img src=@ onerror=alert(123) /&gt;

而是

<img src=@ onerror=alert(123) />

我们可以这样论证:

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

看弹出的x值就知道了,如下图所示。


出现这个结果的原因如下: onclick 里的这段 JavaScript 出现在 HTML 标签内,意味着这里的JavaScript 可以进行 HTML 形式的编码,这种编码有以下两种。

  • 进制编码: &#xH; (十六进制格式)、 &#D; (十进制格式),最后的分号(;)可以不要。
  • HTML 实体编码:即上面的那个 HtmlEncode

在 JavaScript 执行之前,HTML 形式的编码会自动解码。所以样例 0 与样例 B 的意义是一样的,而样例 A 就不一样了。下面我们继续完善这些例子。

上面的用户输入是出现在 HTML 里的情况,如果用户输入出现在 <script> 里的 JavaScript 中,情况会怎样,代码如下:

<input type="button" id="exec_btn" value="exec" />
<script>
function $(id){return document.getElementById(id);};
$('exec_btn').onclick = function(){
      document.write("<img src=@ onerror=alert(123) />");
      //document.write("<img src=@ onerror=alert(123) />");