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

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

本文介绍了如何利用最近推出的问题标签页,实现开发者工具支持来调试内容安全政策 (CSP) 问题。

实现工作是在 2 次实习期间完成的: 1. 在第一个阶段,我们构建了一般报告框架,并针对 3 项 CSP 违规问题设计了问题消息。2. 在第二次会议期间,我们添加了可信类型问题以及一些用于可信类型调试的专用 DevTools 功能。

什么是内容安全政策?

借助内容安全政策 (CSP),您可以限制网站中的某些行为,以提高安全性。例如,CSP 可用于禁止内嵌脚本或禁止 eval,这两者都会减少遭遇跨站脚本攻击 (XSS) 的攻击面。如需详细了解 CSP,请点击此处

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

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

每项政策都可以采用以下任一模式运行:

  • 强制模式 - 其中每项违反政策的行为都属于错误,
  • 仅报告模式 - 将错误消息报告为警告,但不会导致网页失败。

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

此工作的目标是改进 CSP 问题的调试体验。在考虑新问题时,DevTools 团队大致遵循以下流程:

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

第 1 步:为 CSP 问题定义用户故事

在开始实现工作之前,我们创建了一个包含用户故事的设计文档,以便更好地了解我们需要做什么。例如,我们写下了以下用户故事:


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


为了探索此用户故事,我们创建了一些简单的示例网页,这些网页存在我们感兴趣的 CSP 违规问题,我们探索了这些示例网页,以便熟悉该流程。 以下是一些网页示例(打开演示并打开问题标签页):

通过此流程,我们了解到,源代码位置是调试 CSP 问题最重要的信息。我们还发现,在资源被屏蔽时,快速查找关联的 iframe 和请求很有用,此外,在 DevTools 的 Elements 面板中提供指向 HTML 元素的直接链接也很有用。

第 2 步:前端实现

我们将这一洞见转化为我们希望通过 Chrome DevTools Protocol (CDP) 向 DevTools 提供的信息的初稿:

以下是 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 生成 DevTools 前端依赖的 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++ 库,用于处理从 C++ Chromium 后端生成这些数据结构并将其发送到 DevTools 前端。使用该库,您可以使用以下 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 开发者工具协议 (CDP),我们需要找到后端中实际提供这些信息的位置。幸运的是,CSP 代码中已经有一个用于报告模式的瓶颈,我们可以通过以下方式进行钩接:ContentSecurityPolicy::ReportViolation 会将问题报告到可在 CSP HTTP 标头中配置的(可选)报告端点。我们想要报告的大部分信息已经可用,因此无需对后端进行重大更改,我们的插桩即可正常运行。

第 4 步:保存和显示问题

一个小问题是,我们还希望报告在 DevTools 打开之前发生的问题,这与处理控制台消息的方式类似。这意味着,我们不会立即向前端报告问题,而是使用一个存储空间来填充问题,而无论 DevTools 是否处于打开状态,该存储空间都会填充问题。打开 DevTools(或连接任何其他 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 问题。

调试 Trusted Types 问题

如果没有合适的开发者工具,大规模使用 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 会检查该违规行为是否应触发断点,如果是,则会向前端发送消息以暂停执行。

已完成的工作和后续步骤

自引入此处所述问题以来,问题标签页发生了许多变化:

今后,我们计划使用问题标签页显示更多问题,这样一来,我们就可以从长远角度来解决控制台中无法读取的错误消息流问题。

下载预览渠道

不妨考虑将 Chrome Canary 版开发者版Beta 版用作默认开发浏览器。通过这些预览版渠道,您可以使用最新的 DevTools 功能、测试尖端的 Web 平台 API,并帮助您在用户发现问题之前发现网站上的问题!

与 Chrome DevTools 团队联系

您可以使用以下选项讨论与 DevTools 相关的新功能、更新或任何其他内容。