jQuery 3.1.1 违反CSP指令

jQuery 3.1.1 violation of CSP directive

我正在使用 jQuery 版本 3.1.1,并且我正在尝试在我的网页上实施 Content Security Policy (CSP) 指令。

我收到以下错误:

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' 'nonce-c20t41c7-73c6-4bf9-fde8-24a7b35t5f71'". Either the 'unsafe-inline' keyword, a hash ('sha256-KAcpKskREkEQf5B3mhDTonpPg34XnzaUC5IoBrOUrwY='), or a nonce ('nonce-...') is required to enable inline execution.

错误发生在主 jquery.js 脚本文件的第 82 行。这一行的内容是:

doc.head.appendChild( script ).parentNode.removeChild( script );

基本上,它向 DOM 添加了一个内联脚本标记,这违反了 CSP。

我不想使用 'unsafe-inline'。有没有其他方法可以避免这个错误?

正如您在 CSP 违规中看到的那样,我使用的是 CSP 级别 2(随机数),但它被忽略了。是否有可能(以某种方式)通知 jQuery 在附加脚本标签时使用此随机数?


这就是 HTML 的样子(使用随机数的 Express.js 模板)

<script nonce="<%=nonce%>" type="text/javascript" src="jquery.js"></script>

呈现 HTML 后,脚本标记上的 nonce 属性与服务器发送的 CSP nonce 指令匹配。


它确实适用于普通 JavaScript:

<script nonce="<%=nonce%>" type="text/javascript">
        var userEmail = "<%=user.user.email%>";
</script>

如果没有 nonce 属性,此脚本标记将违反 CSP 指令。

好的,您只想包含 jQuery,因此您需要在 jQuery 脚本之前和删除自定义函数之后重新定义函数 appendChild(以禁用它)。

<script>
    var oldAppend = document.head.appendChild
    document.head.appendChild = function(script){
        if(script && script.tagName=='SCRIPT'){
            document.createElement('fakeHead').appendChild(script)//because script need a parent in the line that create the error
            return script
        }
        return oldAppend.apply(this,arguments)
    }
</script>

<script nonce="<%=nonce%>" type="text/javascript" src="jquery.js"></script>

<script>
    document.head.appendChild = oldAppend
</script>

试试这个。它将重新定义 jQuery:

的追加函数
var oldAppend = $.fn.append
$.fn.append = function($el){
    var dom = ($el instanceOf $) ? $el[0] : $el
    if(dom && dom.tagName=='SCRIPT'){
        this[0].appendChild(dom)
        return this
    }
    return oldAppend.apply(this,arguments)
}

它看起来像是 jQuery 的一个错误或怪癖,它如何附加内联脚本最终会丢弃它们的所有属性,我看不出一个明显的修复方法 为了测试它,我使用了以下 HTML:

<!DOCTYPE html>
<html>

    <head>
        <meta http-equiv="Content-Security-Policy" content="default-src http://localhost 'nonce-123456' ; child-src 'none'; object-src 'none'; script-src 'nonce-123456';">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.js" nonce="123456"></script> <!-- HTML nonce works -->
        <script nonce="123456">
            // This works
            console.log('Inline nonce works');

            // This will also work
            var s = document.createElement('script');
            s.setAttribute('nonce', '123456');
            s.textContent = 'console.log("Dynamically generated inline tag works")';
            document.head.appendChild(s);

            // This won't work
            var s2 = document.createElement('script');
            s2.setAttribute('nonce', '123456');
            s2.textContent = 'console.log("Dynamically generated inline tag appended via jQuery doesn\'t work")';
            $(document.head).append(s2); // This will throw a CSP error
        </script>
    </head>

<body>

</body>

</html>

使用jQuery追加时经过以下过程(略有减少):

  • 创建文档片段并向其附加脚本标签
  • type="false/" 属性应用于脚本标签
  • 删除 type 属性
  • 如果存在 src 属性,它会通过 Ajax 检索脚本(没有进一步调查)
  • 如果不是它运行 DOMEval(node.textContent.replace(rcleanScript, ""), doc)

DomEval 看起来像这样(添加了注释):

doc = doc || document;
var script = doc.createElement( "script" );
script.textContent = code;
doc.head.appendChild( script ).parentNode.removeChild( script );

如您所见,在添加新元素之前没有任何属性会转移到新元素,因此 CSP 失败。

解决方案是只使用本机 JavaScript 来附加元素而不是 jQuery 或者可能等待错误 fix/response 到您的报告。我不确定他们以这种方式为内联脚本标签排除属性的原因可能是安全功能?

以下内容无需 jQuery 即可实现您想要的效果 - 只需将 textContent 属性设置为您的 JavaScript 源代码即可。

var script = document.createElement('script');
script.setAttribute('nonce', '<%=nonce%>');
script.textContent = '// Code here';
document.head.appendChild(script);

所以从本质上讲,为什么该特定行会引发错误是因为附加标签实际上是一个具有相同代码但未应用任何属性的新标签,并且由于它没有 nonce 它被 CSP 拒绝了。

更新:我已经修补 jQuery 来解决这个问题(是 3.1.2-pre 修补但通过所有测试),如果你使用我的最后一个修复我建议更新到这个版本!

缩小:http://pastebin.com/gcLexN7z

未缩小:http://pastebin.com/AEvzir4H

这里有分支:https://github.com/Brian-Aykut/jquery/tree/3541-csp

问题link:https://github.com/jquery/jquery/issues/3541

代码更改:

第 ~76 行将 DOMEval 函数替换为:

function DOMEval( code, doc, attr ) {
    doc = doc || document;
    attr = attr || {};
    var script = doc.createElement( "script" );
    for ( var key in attr ) {
        if ( attr.hasOwnProperty( key ) ) {
            script.setAttribute( key, attr[ key ] );
        }
    }
    script.text = code;
    doc.head.appendChild( script ).parentNode.removeChild( script );
}

将第 5717 行的 attr 添加到 var 语句到

var fragment, first, scripts, hasScripts, node, doc, attr,

将第 5790 行附近的 else 正文更改为:

attr = {};
if ( node.hasAttribute && node.hasAttribute( "nonce" ) ) {
    attr.nonce = node.getAttribute( "nonce" );
}
DOMEval( node.textContent.replace( rcleanScript, "" ), doc, attr );