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

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

Ten post dotyczy implementacji obsługi debugowania problemów z zasadami CSP za pomocą Narzędzi deweloperskich przy użyciu niedawno wprowadzonej karty Problemy.

Prace wdrożeniowe zostały przeprowadzone w ramach 2 stawek: 1. W pierwszej z nich opracowaliśmy ogólne zasady raportowania i opracowaliśmy komunikaty o problemach z 3 problemami związanymi z naruszeniem zasad CSP. 2. W drugim dodaliśmy problemy związane z zaufanym typem oraz niektóre specjalistyczne funkcje Narzędzi deweloperskich do debugowania zaufanych typów.

Co to jest Content Security Policy?

Polityka Content Security Policy (CSP) umożliwia ograniczenie niektórych zachowań w witrynie w celu zwiększenia bezpieczeństwa. Za pomocą CSP można na przykład zablokować dostęp do skryptów w treści lub eval. Oba te tryby zmniejszają powierzchnię ataku w przypadku ataków cross-site scripting (XSS). Szczegółowe informacje na temat CSP znajdziesz tutaj.

Szczególnie nowym CSP są zasady Trusted Types(TT), które umożliwiają przeprowadzanie dynamicznej analizy umożliwiającej systematyczne zapobieganie licznych atakom typu „wstrzykiwanie” na strony internetowe. 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ć politykę bezpieczeństwa treści, dodając konkretny 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 do raportowania, w którym komunikat o błędzie jest zgłaszany 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 tych działań było usprawnienie procesu debugowania problemów z CSP. Podczas analizowania nowych problemów zespół Narzędzi deweloperskich postępuje tak:

  1. Definiowanie historii użytkowników. 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 (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 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. Zapisz i wyświetl problemy. Przechowuj problemy w odpowiednim miejscu i udostępniaj je w Narzędziach deweloperskich po otwarciu
  5. Projektowanie tekstu problemów. Przygotuj tekst objaśniający, który pomoże programiście zrozumieć problem i przede wszystkim rozwiązać problem.

Krok 1. Zdefiniowanie historii użytkowników pod kątem problemów związanych z usługami CSP

Przed rozpoczęciem prac wdrożeniowych stworzyliśmy dokument projektowy z historiami użytkowników, aby lepiej zrozumieć, co musimy zrobić. Na przykład napisaliśmy następującą historię użytkownika:


Jako programista, który właśnie zdał sobie sprawę, że część mojej witryny jest zablokowana, chcę: - ...dowiedzieć się, czy CSP jest przyczyną zablokowania elementów iframe / obrazów w mojej witrynie – ...dowiedz się, która dyrektywa CSP powoduje zablokowanie określonego zasobu – ...wiem, jak zmienić CSP w swojej witrynie, aby umożliwić wyświetlanie obecnie zablokowanych zasobów / 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 strony, aby zapoznać się z tym procesem. Oto kilka przykładowych stron internetowych (otwórz wersję demonstracyjną z otwartą kartą Problemy):

Dzięki temu procesowi odkryliśmy, że lokalizacja źródłowa jest najważniejszym elementem podczas debugowania problemów z CSP. Przydatne było też szybkie znalezienie powiązanego elementu iframe i żądania, gdy zasób został zablokowany. Przydatne może być też bezpośredni link do elementu HTML w panelu Elements w Narzędziach deweloperskich.

Krok 2. Implementacja interfejsu

Na tej podstawie stworzyliśmy pierwszą wersję roboczą informacji, które chcemy udostępnić w Narzędziach deweloperskich w ramach protokołu Chrome Dev Tools (CDP):

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

Powyższa definicja 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ę interfejs narzędzi deweloperskich. Na przykład powyższa definicja PDL generuje następujący 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, i prawdopodobnie przede wszystkim, generujemy bibliotekę C++ na podstawie definicji, która obsługuje generowanie i wysyłanie struktur danych z backendu Chromium C++ do interfejsu DevTools. Za jej pomocą obiekt ContentSecurityPolicyIssueDetails można utworzyć za pomocą tego fragmentu kodu w 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 za pośrednictwem protokołu Chrome DevNarzędzia (CDP) w formacie opisanym w poprzedniej sekcji, musieliśmy znaleźć w backendzie miejsce, w którym te informacje rzeczywiście były dostępne. Na szczęście kod CSP miał już wąski gardło używany w trybie tylko do raportowania, dzięki czemu mogliśmy się z nim połączyć: ContentSecurityPolicy::ReportViolation zgłasza problemy w (opcjonalnym) punkcie końcowym raportowania, który można skonfigurować w nagłówku HTTP CSP. Większość informacji, które chcieliśmy zgłosić, była już dostępna, więc nasze narzędzia do działania nie wymagały dużych zmian w backendzie.

Krok 4. Zapisz i wyświetl problemy

Niewielka komplikacja polega na tym, że chcieliśmy też zgłaszać problemy, które wystąpiły przed otwarciem Narzędzi deweloperskich, podobnie jak obsługa wiadomości w konsoli. Oznacza to, że nie zgłaszamy problemów od razu dla interfejsu, ale korzystamy z miejsca na dane wypełnionego problemami niezależnie od tego, czy Narzędzia deweloperskie są otwarte czy nie. Po otwarciu Narzędzi deweloperskich (lub podłączeniu innego klienta CDP) wszystkie wcześniej zarejestrowane problemy można odtworzyć z pamięci.

Prace nad backendem dobiegły końca. Musieliśmy więc skupić się na tym, jak uwidocznić ten problem w interfejsie.

Krok 5. Zaprojektuj tekst 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 opiniach 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 doprecyzowany aż do ukoń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 zajmującego się danym tematem i DevRel opis jest znacznie bardziej zrozumiały i precyzyjny.

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

Rozwiązywanie problemów z zaufanymi typami

Praca z zespołem TT na dużą skalę może być wyzwaniem bez odpowiednich narzędzi dla programistów.

Ulepszone drukowanie w konsoli

Podczas pracy z zaufanymi obiektami chcemy wyświetlać co najmniej taką samą ilość informacji jak w przypadku niezaufanego odpowiednika. Niestety 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 wywołania funkcji .valueOf() w obiekcie. Jednak w przypadku zaufanego typu zwracana wartość nie jest zbyt przydatna. Chcemy otrzymać coś podobnego do tego, co otrzymujesz, gdy dzwonisz pod .toString(). Aby to osiągnąć, musimy zmodyfikować V8 i Blink, aby wprowadzić specjalną obsługę obiektów zaufanego typu.

Mimo że z powodów historycznych taki sposób obsługi został przeprowadzony w wersji 8, ma on poważne 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óżnić koncepcji odpowiadających interfejsowi internetowemu, takich jak zaufany typ. Z tego powodu V8 musi poprosić o pomoc w rozróżnieniu umieszczonego elementu (Blink).

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 umieszczony na stronie komponent może generować własne opisy
  • O wiele łatwiej będzie wygenerować opis za pomocą interfejsu Blink API.
  • Blink ma dostęp do pierwotnej definicji obiektu. Jeśli więc do wygenerowania opisu użyjemy pola .toString(), nie ma ryzyka, że pole .toString() zostanie ponownie zdefiniowane.

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

Obecnie jedynym sposobem debugowania przypadków naruszenia zasad TT jest ustawienie punktów przerwania w wyjątkach JS. Egzekwowane naruszenia zasad TT będą aktywować wyjątek, więc ta funkcja może być przydatna. W rzeczywistych sytuacjach konieczna jest jednak dokładniejsza kontrola nad naruszeniami zasad TT. Szczególnie zależy nam na tym, aby dzielić się tylko naruszeniami zasad dotyczących przejrzystości i uzyskiwania zgody na przetwarzanie danych (a nie innymi wyjątkami), eliminować je również w trybie „tylko raporty” i rozróżniać typy naruszeń zasad dotyczących przejrzystości i uzyskiwania zgody na przetwarzanie danych.

Narzędzia deweloperskie obsługują już wiele różnych punktów przerwania, dzięki czemu architektura jest dość elastyczna. Dodanie nowego typu punktu przerwania wymaga wprowadzenia zmian w backendzie (Blink), CDP i frontendzie. Powinniśmy wprowadzić nowe polecenie CDP, nazwijmy je setBreakOnTTViolation. Frontend będzie używać tego polecenia, aby poinformować backend o rodzaju naruszeń zasad TT, które powinny zostać wykryte. Backend, a w szczególności InspectorDOMDebuggerAgent, udostępnia „sondę” onTTViolation(), która jest wywoływana za każdym razem, gdy wystąpi naruszenie zasad TT. Następnie InspectorDOMDebuggerAgent sprawdzi, czy naruszenie powinno aktywować punkt przerwania. W takim przypadku wyśle do frontendu wiadomość, aby wstrzymać wykonanie.

Co zostało zrobione i co dalej?

Po wprowadzeniu opisanych tu problemów na karcie Problemy wprowadziliśmy sporo 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.

Pobierz kanały podglądu

Zastanów się, czy nie ustawić Chrome w wersji Canary, Dev lub beta jako domyślnej przeglądarki do programowania. Te kanały wersji testowej dają dostęp do najnowszych funkcji Narzędzi deweloperskich, umożliwiają testowanie najnowocześniejszych interfejsów API platformy internetowej i wykrywanie problemów w witrynie, zanim użytkownicy ją zobaczą.

Kontakt z zespołem ds. Narzędzi deweloperskich w Chrome

Użyj poniższych opcji, aby porozmawiać o nowych funkcjach i zmianach w poście lub o innych kwestiach związanych z Narzędziami deweloperskimi.

  • Prześlij nam sugestię lub opinię na crbug.com.
  • Aby zgłosić problem z Narzędziami deweloperskimi, kliknij Więcej opcji   Więcej > Pomoc > Zgłoś problemy z Narzędziami deweloperskimi w Narzędziach deweloperskich.
  • Opublikuj tweeta na stronie @ChromeDevTools.
  • Napisz komentarz pod filmem dotyczącym nowości w Narzędziach deweloperskich w Narzędziach deweloperskich w YouTube lub filmach w YouTube ze wskazówkami dotyczącymi Narzędzi deweloperskich.