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

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

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

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

什么是内容安全政策?

内容安全政策 (CSP) 可限制网站上的某些行为,以提高安全性。例如,CSP 可用于禁止内嵌脚本或 eval,这两者均有助于缩小跨站脚本攻击 (XSS) 攻击的攻击面。如需详细了解 CSP,请参阅此处的内容。

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

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

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

  • 强制模式 - 在这种模式下,每一项政策违规行为都为错误
  • “仅限报告”模式 - 将错误消息报告为警告,但不会导致网页失败。

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

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

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

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

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


身为开发者,刚刚意识到自己网站的部分内容被屏蔽了,我想:- - ...了解 CSP 是否会导致网站上阻止 iframe / 图片 - ...了解哪个 CSP 指令导致特定资源被阻塞 - ...了解如何更改网站的 CSP,以显示当前屏蔽的资源 / 执行当前屏蔽的 js。


为了探究这个用户故事,我们制作了一些简单的示例网页,展示了我们感兴趣的内容安全政策违规行为,并浏览了这些示例网页以自行熟悉该流程。 以下是一些示例网页(在打开问题标签页的情况下打开演示版):

通过此过程,我们了解到来源位置是调试 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++ 库。使用该库,可以使用以下代码段创建 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 步:保存并显示问题

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

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

第 5 步:设计问题文字

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

通常,DevTools 团队会从他们的想法编写一份粗略的草稿:


## 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() 调用。不过,对于 Trusted Type,返回的值不是很有用。相反,我们希望您在调用 .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 版用作您的默认开发浏览器。通过这些预览渠道,您可以访问最新的开发者工具功能,测试先进的网络平台 API,并在用户之前发现您网站上的问题!

与 Chrome 开发者工具团队联系

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

  • 请通过 crbug.com 提交建议或反馈。
  • 使用更多选项报告开发者工具问题 展开 >帮助 >在开发者工具中报告开发者工具问题
  • 请发送电子邮件至 @ChromeDevTools
  • 请对我们的开发者工具新功能 YouTube 视频或开发者工具提示 YouTube 视频发表评论。