JQuery 3.1.1违反了CSP指令

时间:2017-02-25 00:46:31

标签: jquery content-security-policy

我正在使用JQuery 3.1.1版,我正在尝试在我的网页上实施内容安全策略指令。

我收到以下错误:

  

拒绝执行内联脚本,因为它违反了以下内容   内容安全策略指令:“script-src'self'   'nonce-c20t41c7-73c6-4bf9-fde8-24a7b35t5f71'“。要么是   'unsafe-inline'关键字,哈希   ('sha256-KAcpKskREkEQf5B3mhDTonpPg34XnzaUC5IoBrOUrwY =')或者一个随机数   ('nonce -...')是启用内联执行所必需的。

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

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

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

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

正如您在CSP违规中看到的那样,我正在使用CSP级别2(nonce),但它会被忽略。在附加脚本标记时,是否可以(某些方式)通知jQuery使用此nonce?

更新:这就是HTML的样子(使用Express模板表示nonce)

<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指令。

2 个答案:

答案 0 :(得分:2)

看起来像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会失败。

解决方案是使用本机JS附加元素而不是jQuery,或者可能等待对报告的错误修复/响应。我不确定他们的推理是什么,以这种方式为内联脚本标签排除属性可能是一个安全功能?

以下应该在没有jQuery的情况下实现你想要的 - 只需将textContent属性设置为你的JS源代码。

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

基本上,为什么特定行抛出错误的原因是附加的标记实际上是一个具有相同代码且没有应用属性的新标记,因为它没有nonce它被CSP拒绝。

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

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

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

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

问题链接:https://github.com/jquery/jquery/issues/3541

代码中的更改:

Line~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 );  

答案 1 :(得分:1)

好吧,你只想要包含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)
}