【问题标题】:Is it safe for a service worker to return cached CSP headers containing nonces?服务工作者返回包含随机数的缓存 CSP 标头是否安全?
【发布时间】:2021-07-21 15:35:45
【问题描述】:

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

由于 nonce 值不得多次使用,我假设 Service Worker 应该通过修改从预缓存。因此,当向index.html 发出请求时,Service Worker 应该生成一个 nonce,修改 CSP 标头的 script-src 指令以包含新的 nonce,并且 index.html 必须将所有 nonce="" 属性更新为新的 nonce也是。

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

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

【问题讨论】:

    标签: javascript security service-worker content-security-policy workbox


    【解决方案1】:

    主要问题不在于生成随机数的安全性,而在于无法应用服务工作者生成的随机数(至少我没有看到)。

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

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

    但是通过脚本修改缓存页面时的浏览器行为,需要另外检查。 AFAIK 有一种方法可以通过缓存页面绕过 CSP 和 nonce。

    更新

    在某些情况下,您可以在 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 用户会不太安全,但您可以使用真正的主机源 (https://example.com https://CDN.com) 而不是 http:

    【讨论】:

    • 有趣的是,我的服务工作者似乎正在传递缓存的 HTTP 标头,尽管这可能是因为它首先访问了网络(这是一个 StaleWhileRevalidate 缓存策略)。我没有任何内联脚本,它们都是外部脚本,我想知道是否应该对外部脚本使用散列。似乎在外部加载脚本的完整性检查主题方面存在很大的不一致。
    • 我不是 100% 确定服务工作者是否交付(或不交付)HTTP 标头 - 我没有检查。但是在 CSP 测试中,我在 Firefox 中使用标准浏览器缓存时遇到了麻烦——看起来它从缓存中恢复页面而没有 HTTP 标头。 HTTP 标头也存在问题:某些 ISP 会缓存页面和标头 - 您更改了 CSP,但之前的 CSP 会在 1-2 周内应用于某些用户。参考外部脚本的哈希 - 我可以确认 Firefox 和 Safari 没有实现哈希以允许外部脚本。您可以使用一个技巧 - 请在更新的答案中查看详细信息。
    • 我还注意到,在 chrome 中使用禁用缓存选项不会阻止 CSP 标头被缓存,因此在测试时您需要不断地打开和关闭浏览器。我很感激这些提示,需要更多关于这方面的信息!
    • 另外,我试图为 Service Workers 生成 nonce 的解决方案涉及编写一个工作箱插件。我手动创建了 PrecacheController 并传递了实现 handlerWillRespond 的插件。拦截内容类型为text/html的响应,使用RegEx将旧的nonce替换为新的nonce,最后更新headers并返回修改后的响应。这里的限制是网络工作者没有 Crypto API,所以我无法生成强随机数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-05
    • 1970-01-01
    • 2016-02-04
    • 2014-03-04
    • 2020-08-30
    • 2021-11-26
    • 2019-10-26
    相关资源
    最近更新 更多