使用“严格动态”CSP 指令加载脚本的正确方法是什么?

Mec*_*MK1 7 javascript content-security-policy

背景

内容安全策略的想法是告诉网络浏览器从哪里加载什么内容。这意味着攻击者不应该能够注入他们自己的代码,例如,'unsafe-inline'没有明确允许(这不是最好的做法)。

Google 还发布了CSP Evaluator,旨在发现您的政策中可能存在的错误。使用默认设置,该工具建议使用'strict-dynamic'策略'script-src'。它背后的想法是你为你需要的任何 JavaScript 源编写一个加载器,并禁止其他一切。

问题

什么被认为是实现这种加载器的“正确”方法?加载器应该自己编写(例如见下文)还是应该使用工具来创建这样的加载器?(请注意,这个问题并不是要求提供特定的工具推荐)

例子

var imported = document.createElement('script');
imported.src = '/path/to/imported/script';
document.head.appendChild(imported);
Run Code Online (Sandbox Code Playgroud)

语境

我的网站目前有以下政策:

default-src 'none';
img-src 'self';
style-src 'self' https://stackpath.bootstrapcdn.com 'sha256-bviLPwiqrYk7TOtr5i2eb7I5exfGcGEvVuxmITyg//c=';
script-src https://use.fontawesome.com https://code.jquery.com https://cdnjs.cloudflare.com https://stackpath.bootstrapcdn.com;
base-uri 'none';
form-action 'none';
frame-ancestors 'none';
Run Code Online (Sandbox Code Playgroud)

谷歌的工具建议如下:

主机白名单经常被绕过。考虑'strict-dynamic'与 CSP nonces 或 hashes 结合使用。

因此,我想实现一个加载器来加载这些 JS 框架,我想知道如何最好地解决这个问题。

小智 5

一个直接的答案是,只要您动态加载的脚本 ( /path/to/imported/script) 托管在您已经列入白名单的域中,您就script-src不必修改您的 CSP 或更改您的加载程序——一切都会按预期工作.

然而,一个更广泛的问题是,您的script-src白名单包含托管 Javascript 的域,攻击者可以使用这些域在您的应用程序中发现标记注入错误来绕过您的 CSP。例如,攻击者可以使用https://cdnjs.cloudflare.com主机 Angular ( https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.2/angular.min.js) 将 HTML 注入转换为任意脚本执行(是一篇关于此的论文)。

CSP 评估器工具中的建议是将您的应用程序切换为依赖于script-src使用 CSP nonce 而不是白名单的 a。为此,您需要遵循https://csp.withgoogle.com/docs/strict-csp.html中概述的过程——基本上,确保每个<script>元素都有一个正确的nonce属性,该属性会随着每个页面加载而变化,或者而是对静态脚本使用CSP3 哈希值

您的 CSP 将如下所示:

... script-src 'nonce-[random-value]' 'strict-dynamic' 'unsafe-inline' https:; ...

如果您使用'strict-dynamic',则您的脚本加载器不必更改,因为浏览器将自动信任通过编程 API 添加到您的页面的脚本,例如appendChild().

  • 如果“浏览器将自动信任通过编程 API(例如appendChild())添加到页面的脚本”**为真,[这样的 CSP 无法再防止 XSS](https://statuscode.ch/2017/03/ CSP-unsafe-eval-and-jquery/)。链接中的示例:“jQuery(el).html(decodeURIComponent(window.location.search.substr(1)))”。请注意,如果字符串以“use strict”编译指示开头,则“jQuery.globalEval”将使用“appendChild()” (2认同)