使用 Content Security Policies 加固 Firefox 前端

2025年4月9日 • Tom Schuster, Frederik Braun, Christoph Kerschbaumer

包括地址栏和标签页在内的大部分 Firefox 用户界面 (UI) 都是使用标准的 Web 技术(例如 HTML、CSS 和 JavaScript)以及一些额外的自定义组件(如 XUL)实现的。 使用 Web 技术作为前端的一个优点在于,它允许在所有桌面操作系统上使用浏览器引擎渲染前端。然而,就像许多 Web 应用程序容易受到某种形式的注入攻击 (OWASP Top Ten) 一样,Firefox 使用 Web 技术作为前端也不能避免,因此它也容易受到注入攻击。

最著名的注入攻击类型是跨站脚本 (XSS) 攻击。 顾名思义,这些攻击会破坏不同站点之间的边界,并绕过第一道安全防线,例如 同源策略。 与不同站点之间存在边界一样,在父进程中运行的 Firefox UI 与在较低权限内容进程中运行的 Web 内容是分离的。 沙盒化的 Web 内容不应能够将内容(尤其是代码)注入到父进程中。 当然,父进程和子进程之间需要某种方式进行通信 - 否则,例如,在 Web 页面设置其自己的文档标题后,将无法更新 UI 中选项卡的标题。 Firefox 完成这种交互的方式是通过一个名为 IPC 的系统 - 进程间通信

在 Pwn2Own (一个计算机黑客竞赛) 2022 中,一位参与者设法找到了一系列漏洞,使他们能够逃脱 Web 内容沙箱(参见 ZDI 员工的撰写通用介绍LifeOverflow 的视频)。 此漏洞利用链的一部分涉及在 Firefox UI 中创建 JavaScript 内联事件处理程序(使用 setAttribute),然后触发该事件处理程序的执行。 对于网站来说,使用内联事件处理程序缓解 XSS 攻击的常用方法通常涉及使用 Content-Security-Policy (CSP)。 CSP 允许限制允许在给定页面上执行哪些脚本。 通常通过仅允许来自特定 URL 或具有特定哈希值的脚本。 特别是,除非指定 'unsafe-inline',否则浏览器会阻止所有定义内联事件处理程序的尝试。 并且由于 Firefox UI 使用带有特殊功能的 HTML,我们实际上也可以使用 CSP 以相同的方式来加固 Firefox 前端代码。

进展

主要的 Firefox UI,其中包含选项卡、地址栏、菜单栏等,实际上只是一个大的 XHTML 文档,称为 browser.xhtml。 最近,我们总共从这个 XHTML 文档中删除了超过 600 个内联事件处理程序,涉及 50 个错误。 在此,我们要感谢 Firefox 前端团队的支持,没有他们的合作,我们不可能在如此短的时间内完成如此多的更改。

显示 brower.xhtml 中内联事件处理程序数量的图表

图 1:显示 Firefox Nightly 中 browser.xhtml 中内联事件处理程序数量随时间变化的图表。 这些数字是使用 grep 估计的。

插曲:如何替换内联事件处理程序

如果您是 Firefox 开发人员、Firefox 分支(如 Tor Browser)的维护者,或者只是对保护自己的网站感兴趣的 Web 开发人员,那么以下内容可能与您相关。 删除内联事件处理程序的过程通常涉及查找定义内联事件处理程序的所有位置(例如 <button onclick="buttonClicked()">),然后使用对来自新 JS 文件的 addEventListener 的调用替换它。 大致如下:

let button = document.querySelector("button");
button.addEventListener("click", buttonClicked);

但是,作为内联事件处理程序运行的 JS 代码和普通事件处理程序之间存在一些重要的区别。 首先,可以从内联事件处理程序 return false;,这等效于调用 event.preventDefault()。 另请注意,如果将 this(对于内联事件处理程序,它是 event.currentTarget)替换为作为事件侦听器的 箭头函数,则它会发生变化。

展望

虽然 browser.xhtml 包含主要 Firefox UI 的大部分部件,但一些窗口和侧边栏基于它们自己的 (X)HTML 文件。 由于 browser.xhtml 提供了我们前端代码的最大攻击向量,因此我们最初的努力集中在保护和加固 browser.xhtml 上,这已经极大地改善了现状,以防止内联脚本执行。 对于其他窗口(例如“关于 Firefox”对话框),我们会更进一步,添加更严格的 CSP,而不仅仅是阻止脚本。 如果您熟悉 CSP,默认情况下我们会设置一个基线 CSP,如 default-src chrome: resource:;,这基本上意味着我们只允许从 Firefox 附带的文件加载资源。 历史上,我们已经将 CSP 添加到 about: 页面,例如像普通网站一样显示的 about:preferences(参见 Hardening Firefox against Injection Attacks)。 我们的最终目标是完全阻止 Firefox 中的所有动态代码执行(如 eval),以提供尽可能最好和最安全的 Firefox 版本,使其能够抵御任何类型的 XSS 攻击。

总结

我们已经重写了 600 多个 JavaScript 事件处理程序,以减轻主要 Firefox 用户界面中的 XSS 和其他注入攻击。 此缓解措施将在 Firefox 138 中发布。 但是,阻止父进程中脚本的执行并不是终点 - 我们将在不久的将来将此技术扩展到其他上下文中。 还有更多工作要做,因为 UI 需要具有高级权限的 JavaScript API。 但是:我们仍然消除了整整一类攻击,大大提高了攻击者利用 Firefox 的门槛。 事实上,我们希望我们刚刚破坏了某人的漏洞利用链。