Как реализовать отладку CSP и доверенных типов в инструментах разработчика Chrome

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

В этой записи блога рассказывается о том, как в DevTools была реализована поддержка отладки проблем с Content Security Policy (CSP) с помощью недавно добавленной вкладки Issues.

Реализация была выполнена в ходе двух стажировок: 1. На первом этапе мы создали общую структуру отчетов и разработали сообщения о трех нарушениях CSP. 2. Во время второго мы добавили проблемы с доверенными типами наряду с некоторыми специализированными функциями DevTools для отладки доверенных типов.

Что такое Content Security Policy?

Content Security Policy (CSP) позволяет ограничить определенные действия на сайте, чтобы повысить безопасность. Например, с помощью CSP можно запретить встроенные скрипты или eval, что снижает направления атак для межсайтового скриптинга (XSS). Подробные сведения о CSP приведены в этой статье.

Относительно новым является правило Trusted Types(TT), которое позволяет выполнять динамический анализ и систематически предотвращать атаки с внедрением кода на сайтах. Для этого TT поддерживает на сайте контроль кода JavaScript, чтобы разрешить назначение только определенных типов объектов приемникам DOM, таким как innerHTML.

Чтобы активировать Content Security Policy, на сайте нужно добавить определенный HTTP-заголовок. Например, заголовок content-security-policy: require-trusted-types-for 'script'; trusted-types default активирует правила доверенных типов для страницы.

Каждое правило может работать в одном из следующих режимов:

  • Режим принудительного применения. Каждое нарушение правил считается ошибкой.
  • Режим только для отчетов. Сообщение об ошибке регистрируется как предупреждение, но не приводит к сбою на веб-странице.

Проблемы с реализацией Content Security Policy на вкладке Проблемы

Целью этой работы было улучшить процесс отладки проблем CSP. При рассмотрении новых проблем команда DevTools примерно следует следующему процессу:

  1. Определение пользовательских историй. Определите набор пользовательских историй в интерфейсе инструментов разработчика, которые описывают, как веб-разработчик будет исследовать проблему.
  2. Реализация на стороне клиента. На основе пользовательских историй определите, какая информация необходима для исследования проблемы в интерфейсе (например, связанный запрос, название файла cookie, строка в скрипте или HTML-файле и т.д.).
  3. Обнаружение проблем. Определите, в каких местах браузера Chrome можно обнаружить проблему, и настройте отправку отчетов об ошибках, включая информацию, полученную на шаге 2.
  4. Сохраните и посмотрите проблемы. Сохранять проблемы в подходящем месте и делать их доступными для инструментов разработчика после открытия.
  5. Оформление текста Придумайте пояснительный текст, который поможет веб-разработчику понять и, что более важно, устранить проблему.

Шаг 1. Определение пользовательских историй для проблем с CSP

Прежде чем приступить к реализации, мы создали документ с описанием проекта и пользовательскими историями, чтобы лучше понять, что нам нужно сделать. Например, мы записали следующую пользовательскую историю:


Я разработчик и только что понял, что некоторые части моего сайта заблокированы. Я хочу:- ...выяснить, является ли CSP причиной блокировки фреймов iframe и изображений на моем сайте; - ...узнать, какая директива CSP вызывает блокировку определенного ресурса; - ...понять, как изменить CSP моего сайта, чтобы разрешить показ заблокированных ресурсов и выполнение заблокированного кода JavaScript.


Чтобы изучить эту пользовательскую историю, мы создали несколько простых примеров веб-страниц, на которых были продемонстрированы интересующие нас нарушения CSP, и изучили эти примеры страниц, чтобы ознакомиться с процессом. Вот несколько примеров веб-страниц (откройте демоверсию с открытой вкладкой Проблемы):

В результате мы выяснили, что для отладки проблем с CSP важнее всего знать местоположение источника. Мы также обнаружили, что в случае блокировки ресурса полезно быстро найти связанный iframe и запрос, а также что может быть полезна прямая ссылка на элемент HTML на панели Элементы в Инструментах разработчика.

Шаг 2. Реализация на стороне клиента

На основе этого мы создали первый черновик информации, которую хотели сделать доступной в DevTools через Chrome DevTools Protocol (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 (Protocol Data Language). PDL используется для двух целей. Сначала мы используем PDL для создания определений TypeScript, на которые опирается интерфейс DevTools. Например, приведенное выше определение 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. С помощью этой библиотеки можно создать объект ContentSecurityPolicyIssueDetails, используя следующий фрагмент кода C++:

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 Protocol (CDP) в формате, описанном в последнем разделе, нам нужно было найти место, где информация была фактически доступна в серверной части. К счастью, в коде CSP уже было узкое место, используемое для режима только отчетов, к которому мы могли подключиться: ContentSecurityPolicy::ReportViolation сообщает о проблемах в (необязательную) конечную точку отчетов, которую можно настроить в HTTP-заголовке CSP. Большая часть информации, которую мы хотели включить в отчет, уже была доступна, поэтому для работы наших инструментов не потребовалось вносить серьезные изменения в серверную часть.

Шаг 4. Сохраните и покажите проблемы

Небольшая сложность заключается в том, что мы также хотели сообщать о проблемах, возникших до открытия DevTools, аналогично тому, как обрабатываются сообщения консоли. Это значит, что мы не сообщаем о проблемах сразу в интерфейс, а используем хранилище, которое заполняется проблемами независимо от того, открыты DevTools или нет. После открытия DevTools (или подключения любого другого клиента CDP) все ранее записанные проблемы можно воспроизвести из хранилища.

На этом работа с серверной частью была завершена, и мы перешли к клиентской.

Шаг 5. Разработка текста сообщения

В составлении текста сообщений о проблемах участвует несколько команд. Например, мы часто полагаемся на информацию от команды, которая реализует функцию (в данном случае это команда CSP), и, конечно, от команды DevRel, которая разрабатывает способы решения определенных типов проблем для веб-разработчиков. Обычно текст проблемы дорабатывается до тех пор, пока не будет готов.

Обычно команда 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() для объекта. Однако в случае с доверенными типами возвращаемое значение не очень полезно. Вместо этого мы хотели бы получить что-то похожее на то, что вы получаете при вызове .toString(). Для этого нам нужно изменить V8 и Blink, чтобы ввести специальную обработку для объектов доверенных типов.

Хотя по историческим причинам эта специальная обработка выполнялась в V8, у такого подхода есть важные недостатки. Существует множество объектов, которые требуют специального отображения, но имеют один и тот же тип на уровне JS. Поскольку V8 – это чистый JS, он не может различать концепции, соответствующие Web API, такие как доверенный тип. Поэтому V8 запрашивает помощь у своего встраиваемого модуля (Blink), чтобы различать их.

Поэтому перенос этой части кода в Blink или любой другой встраиваемый модуль кажется логичным решением. Помимо решения проблемы с раскрытием данных, у этого подхода есть и другие преимущества:

  • У каждого эмбеддера может быть собственная генерация описаний.
  • Сгенерировать описание с помощью Blink API намного проще.
  • У Blink есть доступ к исходному определению объекта. Таким образом, если мы используем .toString() для создания описания, нет риска, что .toString() может быть переопределен.

Прерывание при нарушении (в режиме "Только отчет")

В настоящее время единственный способ отладки нарушений доверенных типов – это установка точек останова на исключениях JS. Поскольку принудительные нарушения TT вызовут исключение, эта функция может быть в некоторой степени полезной. Однако в реальных сценариях вам нужен более детальный контроль над нарушениями TT. В частности, мы хотим, чтобы прерывание происходило только при нарушениях доверенных типов (а не при других исключениях), в том числе в режиме "Только отчеты", и чтобы можно было различать разные типы нарушений доверенных типов.

Инструменты разработчика уже поддерживают множество точек останова, поэтому архитектура достаточно расширяема. Чтобы добавить новый тип точки останова, нужно внести изменения в серверную часть (Blink), CDP и клиентскую часть. Мы должны ввести новую команду CDP, назовем ее setBreakOnTTViolation. Эта команда будет использоваться интерфейсом, чтобы сообщить серверной части, на каких нарушениях TT она должна прерываться. Бэкенд, в частности InspectorDOMDebuggerAgent, предоставит "зонд" onTTViolation(), который будет вызываться каждый раз, когда происходит нарушение доверенных типов. Затем InspectorDOMDebuggerAgent проверит, должно ли это нарушение активировать точку останова, и, если да, отправит сообщение во внешний интерфейс, чтобы приостановить выполнение.

Что сделано и что дальше?

С момента появления описанных здесь проблем вкладка Проблемы претерпела значительные изменения:

В дальнейшем мы планируем показывать на вкладке Проблемы больше ошибок, чтобы в консоли не появлялось слишком много сообщений о них.

Как скачать предварительные версии каналов

Рекомендуем использовать в качестве браузера для разработки Chrome Canary, Dev или Beta. Эти предварительные версии каналов дают доступ к новейшим функциям инструментов разработчика, позволяют тестировать передовые API веб-платформ и помогают находить проблемы на сайте до того, как с ними столкнутся пользователи.

Как связаться с командой инструментов разработчика Chrome

Ниже перечислены способы обсудить новые функции, обновления и другие вопросы, связанные с инструментами разработчика.