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 nad wdrożeniem zostały wykonane w ramach 2 staży: 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. Aby to osiągnąć, TT pomaga witrynie w kontrolowaniu kodu JavaScriptu, aby zezwalać na przypisywanie do odbiorników DOM, takich jak innerHTML, tylko określonych typów 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 na stronie.

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 raportów – raportuje komunikat o błędzie jako ostrzeżenie, ale nie powoduje błędu na stronie internetowej.

Implementowanie problemów związanych z 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. Określ zestaw scenariuszy użytkownika w części front-endowej Narzędzi deweloperskich, który obejmuje sposób, w jaki programista powinien zbadać problem.
  2. Implementacja front-endu. Na podstawie historii użytkowników określ, jakie informacje są wymagane do zbadania problemu w interfejsie (np.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 można wykryć problem w Chrome, i zautomatyzuj te miejsca, aby zgłosić problem, podając odpowiednie informacje z etapu (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 dotyczącego 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ługą CSP

Zanim rozpoczęliśmy pracę nad wdrażaniem, 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 przetestować tę historię użytkownika, utworzyliśmy kilka prostych przykładowych stron internetowych, na których występowały interesujące nas naruszenia zasad CSP, a następnie je zbadaliśmy, aby poznać ten proces. 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żniejszym elementem informacji, który pozwala debugować problemy związane 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 pliku 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 on napisany w prostym języku PDL (protocol data language). PDL służy do 2 celów. 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 podjęciu decyzji, które informacje chcemy udostępnić, musieliśmy sprawdzić, gdzie w Chromium można je znaleźć.

Krok 3. Wykrywanie problemów

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 te 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 przedstawić problem w interfejsie.

Krok 5. Projektowanie tekstu dotyczącego problemów

Projektowanie tekstu dotyczącego problemów to proces, który wymaga zaangażowania kilku zespołów oprócz naszego. Często korzystamy z opinii zespołu, który wdraża daną funkcję (w tym przypadku byłby to zespół CSP), oraz oczywiście zespołu DevRel, który określa, jak deweloperzy powinni radzić sobie z danym typem problemu. Tekst problemu jest zwykle ulepszany, aż do jego zakończenia.

Zespół DevTools zwykle zaczyna od stworzenia szkicu:


## 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 kilku iteracjach doszliśmy do tego:

ALT_TEXT_HERE

Jak widać, zaangażowanie zespołu ds. funkcji i DevRel sprawia, że opis jest znacznie bardziej przejrzysty i precyzyjny.

Problemy ze stroną związane z CSP możesz też znaleźć na karcie poświęconej wyłącznie 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

W przypadku zaufanych obiektów chcemy wyświetlać co najmniej taką samą ilość informacji jak w przypadku nieuczciwych obiektów. Obecnie podczas wyświetlania zaufanego obiektu 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 ich 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 dowolnego wtyczki wydaje się logicznym wyborem. Oprócz wykrytych problemów jest wiele innych zalet:

  • 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.

Przerwanie w przypadku naruszenia (w trybie tylko do raportowania)

Obecnie jedynym sposobem debugowania naruszeń zasad dotyczących treści nieodpowiednich jest ustawienie punktów przerwania w przypadku wyjątków w JS. Ponieważ wymuszone naruszenia zasad TT powodują wyjątek, 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 przerwania 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ć punkt przerwania, a 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 używać karty Problemy do zgłaszania kolejnych problemów, co pozwoli w długim okresie pozbyć się z Konsoli nieczytelnych komunikatów o błędach.

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 wstępnej zapewniają dostęp do najnowszych funkcji DevTools, umożliwiają testowanie najnowocześniejszych interfejsów API platformy internetowej i pomagają znaleźć problemy w witrynie, zanim zrobią to użytkownicy.

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.