jQuery里的html()

在看一个浏览器插件,发现可以在插件内部的页面中插入任意的 html 标签,但是由于 CSP 的限制,只能script-src 'self' 'unsafe-eval'; object-src 'self',并不支持unsafe-inline

出现问题部分的代码为

1
g.html('<iframe src="' + a.url + "?id=" + a.id '" frameborder="0" scrolling="no"></iframe>').show()

其中 a.url 和 a.id 没有过滤可以完全控制。思路很简单,闭合 iframe 标签插入新的标签即可。在测试的时候发现,利用 img 标签的 onerror 事件就会引发 CSP 的报警,而用 script 标签则不会。一脸懵逼。

本地搭建环境,引用最新的 jQuery 库(3.2.1),用同样的方法却发现无论 img 还是 script 标签都会触发 CSP 的报警。两脸懵逼。

问过大佬,大佬说 jQuery 的.html方法调用的是 eval,而 CSP 中允许了 unsafe-eval,所以能执行 JavaScript。三脸懵逼。

从原版插件中找到 jQuery,版本号为 1.8.0,在 jQuery 中搜索 eval 函数的使用,发现只有在 globalEval 中被调用。

1
2
3
4
5
globalEval: function(b) {
b && aa.test(b) && (a.execScript || function(b) {
a.eval.call(a, b)
})(b)
}

既然会调用 eval,那么就在 globalEval 函数中打一下 call stack 吧。在 eval 前插入 console.log(new Error().stack);。得到函数调用栈。

1
2
3
4
5
6
7
8
9
Function.globalEval
HTMLScriptElement.<anonymous>
Function.each
$.domManip
$.append
$.<anonymous>
Function.access
$.html
<anonymous>

函数一直追踪过去可以看到,原来jQuery 的.html方法并不是 innerHTML 这么简单,在这个函数里做了一些判断,会对Ua = /<(?:script|style|link)/i这样的标签进行特殊处理。如果是 script 标签会将内容 eval 执行,导致 CSP 的保护被绕过。

那么为什么最新版的 jQuery 就不存在这个问题了呢?翻了翻源码可以发现,最新版的 globalEval 函数已经变成了在 head 里 append 一个script 标签等 JavaScript 执行完之后删除。

1
2
3
4
5
6
7
8
9
10
11
12
globalEval: function( code ) {
DOMEval( code );
}

function DOMEval( code, doc ) {
doc = doc || document;

var script = doc.createElement( "script" );

script.text = code;
doc.head.appendChild( script ).parentNode.removeChild( script );
}

这样由于不是通过 eval 的方式执行 JavaScript,受到 CSP 的保护。

jQuery 中关于 globalEval 这个函数的变化也是挺有意思的。在 github 上翻了翻 commit 的历史记录。

2013年4月3日的一次commit把 globalEval 从 eval 变成了插 script 标签的方式。
而在09年的某次commit就曾经是插 script 标签的方式。

这中间到底发生了什么 =。=

分享到 评论