Implementowanie debugowania CSP i Zaufanych typów w Narzędziach deweloperskich w Chrome

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

W tym poście na blogu omawiamy implementację obsługi Narzędzi deweloperskich w celu debugowania problemów związanych z Content Security Policy (CSP) za pomocą niedawno wprowadzonej karty Problemy.

Prace wdrożeniowe zostały przeprowadzone w ramach 2 stawek: 1. W pierwszym wdrożono ogólny system zgłaszania i sformułowano komunikaty dotyczące 3 problemów związanych z naruszeniem zasad CSP. 2. W drugim dodaliśmy problemy z zaufanymi typami oraz kilka wyspecjalizowanych funkcji DevTools do debugowania zaufanych typów.

Czym jest standard Content Security Policy?

Content Security Policy (CSP) umożliwia ograniczenie pewnych zachowań w witrynie w celu zwiększenia bezpieczeństwa. Na przykład za pomocą CSP możesz zablokować skrypty wbudowane lub eval. Oba te rozwiązania zmniejszają powierzchnię ataku w przypadku ataków cross-site scripting (XSS). Szczegółowe informacje o CSP znajdziesz tutaj.

Nowością w CSP jest zasada Trusted Types(TT), która umożliwia analizę dynamiczną, która może systematycznie zapobiegać dużej klasie ataków polegających na wstrzykiwaniu kodu w witrynach. W tym celu TT obsługuje w witrynie zasady dotyczące jej kodu JavaScript, tak aby do ujść DOM, np. innerHTML, można było przypisywać tylko określone typy elementów.

Witryna może aktywować standard Content Security Policy, dodając określony nagłówek HTTP. Na przykład nagłówek content-security-policy: require-trusted-types-for 'script'; trusted-types default aktywuje zasadę TT dla strony.

Każda zasada może działać w jednym z tych trybów:

  • tryb wymuszony – w którym każde naruszenie zasad jest błędem,
  • Tryb tylko raportowania – raportuje komunikat o błędzie jako ostrzeżenie, ale nie powoduje błędu na stronie internetowej.

wdrażanie problemów związanych z polityką treści (Content Security Policy) na karcie Problemy;

Celem tego projektu było ulepszenie debugowania problemów z CSP. Rozpatrując nowe problemy, zespół DevTools wykonuje mniej więcej te czynności:

  1. Definiowanie scenariuszy użytkownika. W interfejsie Narzędzi deweloperskich zidentyfikuj zestaw historii użytkowników, które opisują, w jaki sposób programista stron internetowych musi przeanalizować problem.
  2. Implementacja frontendu. Na podstawie historii użytkowników określ, jakie informacje są wymagane do zbadania problemu w interfejsie (na przykład powiązane żądanie, nazwa pliku cookie, wiersz w skrypcie lub pliku HTML itp.).
  3. Wykrywanie problemów. Określ miejsca w przeglądarce, w których w Chrome można wykryć problem, i wybierz miejsce, w którym można zgłosić problem, łącznie z odpowiednimi informacjami z kroku 2.
  4. Zapisywanie i wyświetlanie problemów Przechowuj problemy w odpowiednim miejscu i ustaw je jako dostępne w Narzędziach deweloperskich po ich otwarciu.
  5. Projektowanie tekstu problemów. Utwórz tekst wyjaśniający, który pomoże deweloperowi zrozumieć problem i go rozwiązać.

Krok 1. Definiowanie scenariuszy użytkownika dotyczących problemów z usługami CSP

Zanim rozpoczęliśmy wdrażanie, utworzyliśmy dokument projektowy z historią użytkowników, aby lepiej zrozumieć, co musimy zrobić. Na przykład zapisaliśmy taką historię użytkownika:


Jako deweloper, który właśnie zauważył, że część mojej witryny jest zablokowana, chcę:- - ...sprawdzić, czy CSP jest przyczyną zablokowania ramek iframe lub obrazów w mojej witrynie - ...sprawdzić, która dyrektywa CSP powoduje zablokowanie określonego zasobu - ...wiedzieć, jak zmienić CSP w mojej witrynie, aby umożliwić wyświetlanie obecnie zablokowanych zasobów lub wykonanie obecnie zablokowanego kodu js.


Aby zapoznać się z historią użytkownika, utworzyliśmy kilka prostych przykładowych stron internetowych przedstawiających naruszenia zasad CSP, które nas interesują, i przejrzeliśmy te przykładowe strony, aby zapoznać się z tym procesem. Oto kilka przykładowych stron internetowych (otwórz wersję demonstracyjną z otwartą kartą Problemy):

Dzięki temu dowiedzieliśmy się, że lokalizacja źródła jest najważniejszą informacją potrzebną do debugowania problemów z CSP. Okazało się też, że w przypadku zablokowanego zasobu przydatne jest szybkie znajdowanie powiązanego iframe i żądania. Przydatny może być też bezpośredni link do elementu HTML w panelu Elementy w DevTools.

Krok 2. Implementacja front-endu

Na podstawie tych informacji stworzyliśmy pierwszy szkic informacji, które chcieliśmy udostępnić w Narzędziach deweloperskich za pomocą protokołu CDP (Chrome DevTools Protocol):

Poniżej znajduje się fragment dokumentu 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

Definicja powyżej koduje strukturę danych JSON. Jest napisany w prostym języku PDL (język danych protokołu). PDL jest używane w 2 celach. Najpierw używamy PDL do generowania definicji TypeScript, na których opiera się front-end Narzędzi dla programistów. Na przykład powyższa definicja PDL generuje ten interfejs 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;
}

Po drugie, co jest prawdopodobnie ważniejsze, na podstawie definicji generujemy bibliotekę C++, która obsługuje generowanie i wysyłanie tych struktur danych z back-endu Chromium w C++ do front-endu DevTools. Za pomocą tej biblioteki można utworzyć obiekt ContentSecurityPolicyIssueDetails, korzystając z tego fragmentu kodu C++:

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

Po ustaleniu, jakie informacje chcemy udostępnić, trzeba było zastanowić się, skąd je wziąć z Chromium.

Krok 3. Wykrywanie problemu

Aby udostępnić informacje w ramach protokołu Chrome DevTools Protocol (CDP) w formacie opisanym w poprzedniej sekcji, musieliśmy znaleźć miejsce, w którym informacje są faktycznie dostępne po stronie serwera. Na szczęście kod CSP zawierał już wąskie gardło używane w trybie tylko do raportowania, w którym mogliśmy podłączyć: ContentSecurityPolicy::ReportViolation raportuje problemy do (opcjonalnego) punktu końcowego raportowania, który można skonfigurować w nagłówku HTTP CSP. Większość informacji, które chcieliśmy raportować, była już dostępna, więc nie trzeba było wprowadzać dużych zmian w back-endzie, aby nasza instrumentacja działała.

Krok 4. Zapisz i wyświetl problemy

Niewielką komplikacją jest fakt, że chcieliśmy też zgłaszać problemy, które wystąpiły przed otwarciem DevTools, podobnie jak wiadomości w konsoli. Oznacza to, że nie zgłaszamy problemów bezpośrednio do front-endu, ale używamy magazynu, który jest wypełniany problemami niezależnie od tego, czy narzędzia deweloperskie są otwarte. Po otwarciu DevTools (lub podłączeniu dowolnego innego klienta CDP) wszystkie wcześniej zarejestrowane problemy można odtworzyć z magazynu.

To zakończyło pracę nad backendem. Teraz musieliśmy się skupić na tym, jak pokazać problem w interfejsie.

Krok 5. Projektowanie tekstu dotyczącego problemów

Opracowanie tekstu problemów to proces, w którym angażuje kilka zespołów innych niż nasze. Na przykład często polegamy na danych zespołu wdrażającego daną funkcję (w tym przypadku będzie to zespół CSP) i oczywiście zespołu DevRel, który opracowuje sposoby radzenia sobie z określonym rodzajem problemu. Tekst problemu jest zwykle ulepszany, aż do jego zakończenia.

Zwykle zespół Narzędzi deweloperskich rozpoczyna od wstępnych wersji roboczych:


## 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/

Po iteracji mamy następujące wyniki:

ALT_TEXT_HERE

Jak widać, dzięki zaangażowaniu zespołu ds. funkcji i DevRel opis jest znacznie bardziej przejrzysty i precyzyjny.

Problemy dotyczące CSP na Twojej stronie możesz też znaleźć na karcie poświęconej naruszeniom zasad CSP.

Debugowanie problemów z zaufanymi typami

Bez odpowiednich narzędzi dla programistów praca z TTS na dużą skalę może być trudna.

Ulepszone drukowanie w konsoli

Podczas pracy z zaufanymi obiektami chcemy wyświetlać co najmniej taką samą ilość informacji jak w przypadku niezaufanego odpowiednika. Obecnie podczas wyświetlania obiektu zaufanego nie są wyświetlane żadne informacje o opakowanym obiekcie.

Dzieje się tak, ponieważ wartość wyświetlana w konsoli jest domyślnie pobierana z metody .valueOf() obiektu. W przypadku zaufanych typów zwrócona wartość nie jest jednak zbyt przydatna. Zamiast tego chcielibyśmy mieć coś podobnego do tego, co otrzymujesz, gdy dzwonisz na numer .toString(). Aby to osiągnąć, musimy zmodyfikować V8 i Blink, aby wprowadzić specjalne przetwarzanie obiektów typu zaufany.

Chociaż ze względów historycznych to niestandardowe działanie było wykonywane w V8, takie podejście ma istotne wady. Istnieje wiele obiektów, które wymagają wyświetlania niestandardowego, ale których typ jest taki sam na poziomie JS. Ponieważ V8 to czysty JS, nie może rozróżniać pojęć odpowiadających interfejsom internetowym, takich jak zaufany typ. Z tego powodu V8 musi poprosić o pomoc w odróżnieniu ich od siebie.

Dlatego przeniesienie tej części kodu do Blink lub w dowolnej usłudze umieszczania treści wydaje się sensownym rozwiązaniem. Poza tym problemem wiąże się z wieloma innymi korzyściami:

  • Każdy użytkownik może generować własny opis.
  • Generowanie opisu jest znacznie łatwiejsze za pomocą interfejsu Blink API.
  • Blink ma dostęp do pierwotnej definicji obiektu. Jeśli więc do wygenerowania opisu użyjemy wartości .toString(), nie ma ryzyka, że wartość .toString() zostanie zdefiniowana ponownie.

Przypadek naruszenia zasad (w trybie tylko do zgłaszania)

Obecnie jedynym sposobem debugowania naruszeń zasad dotyczących treści nieodpowiednich jest ustawienie punktów przerwania w przypadku wyjątków w JS. Egzekwowane naruszenia zasad TT będą aktywować wyjątek, więc ta funkcja może być przydatna. W rzeczywistych sytuacjach potrzebujesz jednak bardziej szczegółowej kontroli nad naruszeniami zasad dotyczących treści. Chcielibyśmy, aby przerwy występowały tylko w przypadku naruszeń zasad dotyczących treści (a nie innych wyjątków), a także w trybie tylko do zgłaszania i aby można było rozróżnić różne typy naruszeń zasad dotyczących treści.

Narzędzie DevTools obsługuje już wiele punktów przełamania, więc architektura jest dość elastyczna. Dodanie nowego typu punktu kontrolnego wymaga wprowadzenia zmian w backendzie (Blink), CDP i interfejsie. Powinniśmy wprowadzić nowe polecenie CDP, nazwijmy je setBreakOnTTViolation. To polecenie będzie używane przez frontend, aby informować backend o tym, jakie naruszenia zasad TT należy naprawić. Backend, w szczególności InspectorDOMDebuggerAgent, udostępni „probe” (sondę), onTTViolation() która będzie wywoływana za każdym razem, gdy wystąpi naruszenie zasad TT. Następnie InspectorDOMDebuggerAgent sprawdzi, czy naruszenie powinno spowodować przerwanie działania. Jeśli tak, wyśle wiadomość do interfejsu, aby wstrzymać wykonanie.

Co zostało zrobione i co dalej?

Od czasu wystąpienia opisanych tu problemów karta Problemy przeszła wiele zmian:

W przyszłości planujemy korzystać z karty Problemy do wykrywania kolejnych problemów, co pozwoli na dłuższą metę usunięcie nieczytelnego procesu komunikatów o błędach z konsoli.

Pobieranie kanałów podglądu

Rozważ użycie przeglądarki Chrome Canary, Dev lub Beta jako domyślnej przeglądarki deweloperskiej. Te kanały wersji testowej dają dostęp do najnowszych funkcji Narzędzi deweloperskich, umożliwiają testowanie najnowocześniejszych interfejsów API platform internetowych i pomagają w wykrywaniu problemów w witrynie przed użytkownikami.

Kontakt z zespołem Narzędzi deweloperskich w Chrome

Aby omówić nowe funkcje, aktualizacje lub inne kwestie związane z Narzędziami deweloperskimi, skorzystaj z tych opcji.