return 包含随机数的缓存 CSP headers 对于服务工作者来说是否安全?

Is it safe for a service worker to return cached CSP headers containing nonces?

我有一个用 Angular 编写的单页应用程序,使用的是 Workbox 支持的服务工作者。静态应用程序文件与包含随机数的 Content-Security-Policy header 一起提供。包含 <script> 标签的静态应用程序文件(例如 index.html)使用随机数属性(<script nonce="FCNjs05n4eQdfn39fn3c9h5segb">)修饰。

由于 nonce 值 不能 使用超过一次,我假设 Service Worker 应该通过以下方式为每个对静态应用文件的请求生成新的 nonce 值修改从预缓存传递的响应。因此,当向 index.html 发出请求时,Service Worker 应该生成一个随机数,修改 CSP header 的 script-src 指令以包含新的随机数,并且 index.html还必须将所有 nonce="" 属性更新为新随机数。

我假设这是必要的,因为重复使用相同的 nonce 值可能会带来安全风险;但是,我不确定这一点,因为没有针对此特定场景的明确安全建议。我找到了一个代码示例,其中作者演示了使用当前日期和请求的文件名在 Service Worker 中生成一个新的随机数(并且我的推理是不安全的,因为它不使用加密强度 pseudo-random 字节).

还应注意,我不能使用散列,因为我 运行 遇到了 Mozilla Firefox 和 Safari 的障碍。 Firefox 只会计算内联脚本标签内容的哈希值并将其与您的 Content-Security-Policy header 中存在的哈希值进行比较。外部 JavaScript 源(例如 <script src="js/foo.js"></script>)的哈希值是 NOT 计算出来的,并与您在 CSP header 中输入的哈希值进行了比较。这使得构建安全的应用程序变得复杂,因为它需要我依赖不同的安全机制(随机数),这反过来又需要我在我的服务器和服务工作者中采取额外的步骤来在 header 和交付的内容。

主要问题不在于生成 nonce 的安全性,而是在于无法应用 service worker 生成的 nonce(至少我没有看到)。

据我了解,不会为缓存页面存储 HTTP header,因此当从缓存中恢复页面时,CSP HTTP header 将被忽略。将元标记添加到缓存页面(如果技术上可行)会导致它将成为唯一一个交付的 CSP,因此它应该按预期工作。

另一方面,如果您 add/remove/change <meta http-equiv='Content-Security-Policy' content=''> 元标记,浏览器会记住并应用所有以前的策略,请参阅测试。因此,您将同时拥有多个 CSP。
您需要重新加载页面以清除浏览器“内存”,这是 SPA 与 CSP 的主要问题。

但是浏览器在通过脚本修改缓存页面时的行为需要额外检查。据我所知,有一种方法可以通过缓存页面绕过 CSP 和随机数。

更新

在某些情况下,您可以在 Firefox 和 Safari 中使用外部脚本哈希的解决方法。您可以使用内联脚本来加载外部脚本,例如:

var external = document.createElement("script");
external.src = "http://example.com/script.js"
external.setAttribute("type", "text/javascript");
document.head.appendChild(external);

var external2 = document.createElement("script");
external2.src = "http://example.com/script2.js"
external2.setAttribute("type", "text/javascript");
document.head.appendChild(external2);

上面的内联脚本可以通过 'hash-value''strict-dynamic' 配对使用(它是 Google 的 strict CSP 的变体):

script-src 'sha256-hash_of_inline_script' 'strict-dynamic' https:

'strict-dynamic' 令牌允许加载由合法内联脚本加载的任何脚本。
Safari 不支持 'strict-dynamic',因此它将使用 https: scheme-source 来允许外部脚本。
Chrome 和 Firefox 支持 'strict-dynamic',因此 https: 将被忽略。

是的,Safari 用户会不太安全,但您可以使用真正的 host-sources (https://example.com https://CDN.com) 而不是 http: