在 Chrome 开发者工具中实现 CSP 和可信类型调试

Kateryna Prokopenko
Kateryna Prokopenko
Alfonso Castaño
Alfonso Castaño

这篇博文介绍的是如何在最近引入的问题标签页的帮助下实现开发者工具功能,以便调试内容安全政策 (CSP) 问题。

实施工作在 2 个实习期间完成: 1. 第一次,我们构建了常规报告框架,并针对 3 个 CSP 违规问题设计问题消息。 2. 在第二部分,我们添加了“可信类型”问题,以及用于调试可信类型的一些专用开发者工具功能。

什么是内容安全政策?

内容安全政策 (CSP) 允许限制网站中的某些行为,以便提高安全性。例如,CSP 可用于禁止内联脚本或禁止 eval,这两种方式都可以减少跨站脚本 (XSS) 攻击的攻击面。如需详细了解 CSP,请参阅此处

可信类型(TT) 政策是一项特别新的 CSP,可用于进行动态分析,以便系统地阻止针对网站的大量注入攻击。为此,TT 支持网站监管其 JavaScript 代码,以便仅允许将某些类型的内容(例如 innerHTML)分配给 DOM 接收器。

网站可以通过添加特定的 HTTP 标头来启用内容安全政策。例如,标头 content-security-policy: require-trusted-types-for 'script'; trusted-types default 可激活网页的 TT 政策。

每项政策都可以通过以下某种模式运行:

  • 强制模式 - 在此模式下,每次违反政策都是一个错误;
  • 仅报告模式 - 将错误消息报告为警告,但不会导致网页出错。

问题标签页中实施内容安全政策问题

这项工作的目标是改善 CSP 问题的调试体验。在考虑新问题时,开发者工具团队大致会遵循以下流程:

  1. 定义用户故事。确定一组在开发者工具前端中的用户案例,介绍 Web 开发者需要如何调查问题。
  2. 前端实现。根据用户案例,确定需要哪些信息来调查前端中的问题(例如,相关请求、Cookie 的名称、脚本或 HTML 文件中的一行等)。
  3. 问题检测。确定在 Chrome 中可在浏览器中检测到相应问题的位置,并插桩以便报告问题,包括第 (2 步) 中的相关信息。
  4. 保存并显示问题。将问题存储在适当位置,并在打开后将其提供给开发者工具
  5. 设计问题文本。想出一段说明性文字,可帮助 Web 开发者理解,更重要的是,要解决该问题

第 1 步:为 CSP 问题定义用户案例

在开始实施工作之前,我们创建了一份包含用户案例的设计文档,以便更好地了解我们需要执行的操作。例如,我们撰写了以下用户案例:


作为开发者,刚刚意识到我网站的某些部分被屏蔽,我想:- - ...了解 CSP 是否是我网站上被屏蔽 iframe / 图片的原因 - ...了解哪些 CSP 指令导致特定资源被屏蔽 - 知道如何更改网站的 CSP 以允许显示当前被屏蔽的资源 / 执行当前被屏蔽的 js。


为探究这个用户案例,我们制作了一些简单的示例网页来展示我们感兴趣的 CSP 违规行为,还浏览了示例网页以熟悉相关流程。 以下是一些示例网页(打开演示,并打开问题标签页):

通过此过程,我们了解到来源位置是调试 CSP 问题的最重要信息。我们还发现,在资源被屏蔽时可以快速找到关联的 iframe 和请求,而且开发者工具的 Elements 面板中指向 HTML 元素的直接链接也很有用。

第 2 步:前端实现

我们将这些见解纳入到我们希望通过 Chrome 开发者工具协议 (CDP) 提供给开发者工具的信息初稿:

下面这段代码摘录自 third_party/blink/public/devtools_protocol/browser_protocol.pdl

 type ContentSecurityPolicyIssueDetails extends object
   properties
     # The url not included in allowed sources.
     optional string blockedURL
     # Specific directive that is violated, causing the CSP issue.
     string violatedDirective
     boolean isReportOnly
     ContentSecurityPolicyViolationType contentSecurityPolicyViolationType
     optional AffectedFrame frameAncestor
     optional SourceCodeLocation sourceCodeLocation
     optional DOM.BackendNodeId violatingNodeId

上述定义实质上是对 JSON 数据结构进行编码。它是使用一种称为 PDL(协议数据语言)的简单语言编写的。PDL 用于两种用途。首先,我们使用 PDL 生成开发者工具前端所依赖的 TypeScript 定义。例如,上面的 PDL 定义会生成以下 TypeScript 接口:

export interface ContentSecurityPolicyIssueDetails {
  /**
  * The url not included in allowed sources.
  */
  blockedURL?: string;
  /**
  * Specific directive that is violated, causing the CSP issue.
  */
  violatedDirective: string;
  isReportOnly: boolean;
  contentSecurityPolicyViolationType: ContentSecurityPolicyViolationType;
  frameAncestor?: AffectedFrame;
  sourceCodeLocation?: SourceCodeLocation;
  violatingNodeId?: DOM.BackendNodeId;
}

其次,可能更重要的是,我们根据负责生成这些数据结构并将其从 C++ Chromium 后端发送到开发者工具前端的定义生成 C++ 库。借助该库,可以使用以下 C++ 代码段创建 ContentSecurityPolicyIssueDetails 对象:

protocol::Audits::ContentSecurityPolicyIssueDetails::create()
  .setViolatedDirective(d->violated_directive)
  .setIsReportOnly(d->is_report_only)
  .setContentSecurityPolicyViolationType(BuildViolationType(
      d->content_security_policy_violation_type)))
  .build();

在确定希望提供哪些信息后,我们需要探索从 Chromium 的何处获取这些信息。

第 3 步:问题检测

要按照上一部分所述的格式向 Chrome DevTools 协议 (CDP) 提供信息,我们需要找到在后端中实际提供这些信息的位置。幸运的是,CSP 代码已有瓶颈用于仅限报告模式,我们可以通过此连接:ContentSecurityPolicy::ReportViolation 向(可选)报告端点(可在 CSP HTTP 标头中配置)报告问题。我们想要报告的大部分信息都已经可用,因此无需对后端进行重大更改即可使我们的插桩正常运行。

第 4 步:保存并显示问题

一个小小的复杂问题是,我们还想报告在开发者工具打开之前出现的问题,这与控制台消息的处理方式类似。这意味着我们不会立即向前端报告问题,而是使用一个填充了问题的存储空间,无论开发者工具是否打开。打开开发者工具(或者连接任何其他 CDP 客户端)后,可以从存储空间重放之前记录的所有问题。

后端工作到此结束,现在我们需要重点关注如何在前端显示问题。

第 5 步:设计问题文本

除了我们自己的团队以外,设计问题文本是一个过程涉及多个团队。例如,我们经常依赖实现某项功能的团队(在本例中是 CSP 团队)的见解,当然还有 DevRel 团队,该团队设计 Web 开发者应如何处理特定类型的问题。问题文本通常会进行一些优化,直到完成为止。

一般情况下,开发者工具团队会首先从他们设想的大致内容入手:


## Header
Content Security Policy: include all sources of your resources in content security policy header to improve the functioning of your site

## General information
Even though some sources are included in the content security policy header, some resources accessed by your site like images, stylesheets or scripts originate from sources not included in content security policy directives.

Usage of content from not included sources is restricted to strengthen the security of your entire site.

## Specific information

### VIOLATED DIRECTIVES
`img-src 'self'`

### BLOCKED URLs
https://imgur.com/JuXCo1p.jpg

## Specific information
https://web.dev/strict-csp/

经过迭代,结果为:

ALT_TEXT_HERE

如您所见,让功能团队和 DevRel 参与进来,可以使说明更加清晰明确!

您网页上的 CSP 问题也可以在专门针对 CSP 违规行为的标签页中找到。

调试可信类型问题

如果没有合适的开发者工具,大规模使用 TT 可能并非易事。

改进了控制台打印功能

当处理可信对象时,我们希望显示的信息量至少与不可信对象相同。遗憾的是,目前在显示可信对象时,不会显示有关被封装对象的信息。

这是因为,默认情况下,控制台中显示的值取自对对象调用 .valueOf()。不过,如果是可信类型,返回的值就不是很有用。而是希望获得与调用 .toString() 时获得的内容类似。为了实现这一点,我们需要修改 V8 和 Blink,为可信类型对象引入特殊处理。

尽管由于历史原因,这种自定义处理是在 V8 中完成的,但这种方法存在重要的缺点。有许多对象需要自定义显示,但它们的类型在 JS 级别是相同的。由于 V8 是纯 JS 版本,因此无法区分与 Web API 对应的概念,例如可信类型。因此,V8 必须请求嵌入器 (Blink) 帮助进行区分。

因此,将这部分代码移至 Blink 或任何嵌入器听起来都是合乎逻辑的。除了暴露问题之外,还有许多其他好处:

  • 每个嵌入器都可以有自己的描述生成
  • 通过 Blink API 生成说明变得更轻松
  • Blink 有权访问对象的原始定义。因此,如果我们使用 .toString() 生成说明,不存在重新定义 .toString() 的风险。

违规时(在仅报告模式下)

目前,调试 TT 违规行为的唯一方法是在 JS 异常上设置断点。由于强制执行的 TT 违规行为会触发例外情况,因此该功能可能会非常有用。但在实际场景中,您需要更精细地控制 TT 违规行为。具体而言,我们希望仅破除 TT 违规行为(而非其他例外情况),也想在“仅限报告”模式下进行修正,并区分不同类型的 TT 违规行为。

开发者工具已支持各种断点,因此架构可扩展性非常高。添加新的断点类型需要在后端 (Blink)、CDP 和前端进行更改。我们应该引入一个新的 CDP 命令,我们将其命名为 setBreakOnTTViolation。前端将使用此命令告知后端应该破坏哪种 TT 违规行为。后端(尤其是 InspectorDOMDebuggerAgent)将提供“探测”onTTViolation(),每当发生 TT 违规行为时,系统都会调用该探测。然后,InspectorDOMDebuggerAgent 将检查该违规行为是否应触发断点,如果出现这种情况,它会向前端发送消息以暂停执行。

已经采取的行动和后续行动

自此处所述的问题推出以来,我们对问题标签页进行了一些更改:

今后,我们计划使用 Issues(问题)标签页来发现更多问题,这样,从长远来看,这将使控制台能够卸载无法读取的错误消息流。

下载预览渠道

您可以考虑将 Chrome Canary 版Dev 版Beta 版用作默认开发浏览器。通过这些预览渠道,您可以使用最新的开发者工具功能,测试先进的网络平台 API,并在用户采取行动之前发现网站上的问题!

与 Chrome 开发者工具团队联系

使用以下选项讨论博文中的新功能和变化,或讨论与开发者工具有关的任何其他内容。

  • 通过 crbug.com 提交建议或反馈。
  • 使用开发者工具中的更多选项   了解详情   > Help > Report a DevTools issues来报告开发者工具问题。
  • 发推文:@ChromeDevTools
  • 请在 YouTube 视频或“开发者工具提示”YouTube 视频中留言说明“开发者工具的新变化”。