Zapobiegaj lukom w zabezpieczeniach witryn opartych na DOM, korzystając z zaufanych typów

Krzysztof Kotowicz
Krzysztof Kotowicz

Browser Support

  • Chrome: 83.
  • Edge: 83.
  • Firefox: 148.
  • Safari: 26.

Source

Ataki typu cross-site scripting oparte na DOM (DOM XSS) występują, gdy dane z a źródła kontrolowanego przez użytkownika (np. nazwa użytkownika lub adres URL przekierowania pobrany z fragmentu adresu URL ) docierają do ujścia, czyli funkcji takiej jak eval() lub ustawienia właściwości takiej jak .innerHTML, która może wykonywać dowolny kod JavaScript.

DOM XSS to jedna z najczęstszych luk w zabezpieczeniach internetowych, a zespoły deweloperów często nieświadomie wprowadzają ją w swoich aplikacjach. Trusted Types zapewniają narzędzia do pisania i sprawdzania aplikacji pod kątem bezpieczeństwa oraz do utrzymywania ich bez luk w zabezpieczeniach DOM XSS Domyślnie zabezpieczają one niebezpieczne funkcje interfejsu Web API. Trusted Types są dostępne jako polyfill dla przeglądarek, które jeszcze ich nie obsługują.

Tło

Od wielu lat DOM XSS jest jedną z najczęstszych i najgroźniejszych luk w zabezpieczeniach internetowych.

Istnieją 2 rodzaje ataków typu cross-site scripting. Niektóre luki w zabezpieczeniach XSS są spowodowane kodem po stronie serwera, który w niebezpieczny sposób tworzy kod HTML tworzący witrynę. Inne mają przyczynę po stronie klienta, gdzie kod JavaScript wywołuje niebezpieczne funkcje z treścią kontrolowaną przez użytkownika.

Aby zapobiec atakom XSS po stronie serwera, nie generuj kodu HTML przez łączenie ciągów znaków. Zamiast tego używaj bezpiecznych bibliotek szablonów z automatycznym kontekstowym ucieczką oraz standardu Content Security Policy opartego na nonce aby dodatkowo ograniczyć liczbę błędów.

Przeglądarki mogą też pomóc w zapobieganiu atakom XSS opartym na DOM po stronie klienta, używając Trusted Types.

Wprowadzenie do interfejsu API

Trusted Types działają przez blokowanie tych ryzykownych funkcji ujścia: Niektóre z nich mogą być Ci znane, ponieważ dostawcy przeglądarek i frameworków internetowych już odradzają używanie tych funkcji ze względów bezpieczeństwa.

Trusted Types wymagają przetwarzania danych przed przekazaniem ich do tych funkcji ujścia. Użycie tylko ciągu znaków nie działa, ponieważ przeglądarka nie wie, czy dane są zaufane:

Nie
anElement.innerHTML  = location.href;
Gdy Trusted Types są włączone, przeglądarka zgłasza TypeError i uniemożliwia użycie ujścia DOM XSS z ciągiem znaków.

Aby wskazać, że dane zostały bezpiecznie przetworzone, utwórz specjalny obiekt – Trusted Type.

Tak
anElement.innerHTML = aTrustedHTML;
  
Gdy Trusted Types są włączone, przeglądarka akceptuje obiekt TrustedHTML w przypadku ujść, które oczekują fragmentów kodu HTML. W przypadku innych wrażliwych ujść dostępne są też TrustedScript i TrustedScriptURL obiekty.

Trusted Types znacznie zmniejszają powierzchnię ataku DOM XSS w Twojej aplikacji. Upraszczają sprawdzanie bezpieczeństwa i pozwalają egzekwować oparte na typach kontrole bezpieczeństwa przeprowadzane podczas kompilowania, lintowania lub pakowania kodu w czasie działania w przeglądarce.

Jak używać Trusted Types

Przygotowanie na raporty o naruszeniach standardu Content Security Policy

Możesz wdrożyć kolektor raportów, np. reporting-api-processor lub go-csp-collector (oba są dostępne w ramach open source), albo użyć jednego z komercyjnych odpowiedników. Możesz też dodać niestandardowe logowanie i debugować naruszenia w przeglądarce za pomocą ReportingObserver:

const observer = new ReportingObserver((reports, observer) => {
    for (const report of reports) {
        if (report.type !== 'csp-violation' ||
            report.body.effectiveDirective !== 'require-trusted-types-for') {
            continue;
        }

        const violation = report.body;
        console.log('Trusted Types Violation:', violation);

        // ... (rest of your logging and reporting logic)
    }
}, { buffered: true });

observer.observe();

lub dodając detektor zdarzeń:

document.addEventListener('securitypolicyviolation',
    console.error.bind(console));

Dodawanie nagłówka CSP tylko do raportowania

Dodaj ten nagłówek odpowiedzi HTTP do dokumentów, które chcesz przenieść do Trusted Types:

Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

Teraz wszystkie naruszenia są zgłaszane do //my-csp-endpoint.example, ale witryna nadal działa. W następnej sekcji wyjaśnimy, jak działa //my-csp-endpoint.example.

Identyfikowanie naruszeń Trusted Types

Od teraz za każdym razem, gdy Trusted Types wykryją naruszenie, przeglądarka wysyła raport do skonfigurowanego report-uri. Jeśli na przykład Twoja aplikacja przekaże ciąg znaków do innerHTML, przeglądarka wyśle ten raport:

{
"csp-report": {
    "document-uri": "https://my.url.example",
    "violated-directive": "require-trusted-types-for",
    "disposition": "report",
    "blocked-uri": "trusted-types-sink",
    "line-number": 39,
    "column-number": 12,
    "source-file": "https://my.url.example/script.js",
    "status-code": 0,
    "script-sample": "Element innerHTML <img src=x"
}
}

Wynika z niego, że w https://my.url.example/script.js w wierszu 39 wywołano innerHTML z ciągiem znaków zaczynającym się od <img src=x. Te informacje powinny pomóc Ci zawęzić obszar kodu, który może wprowadzać DOM XSS i wymagać zmiany.

Naprawianie naruszeń

Istnieje kilka sposobów naprawienia naruszenia Trusted Type. Możesz usunąć kod powodujący problem, użyć biblioteki, utworzyć zasadę Trusted Type lub, w ostateczności, utworzyć zasadę domyślną.

Przepisywanie kodu powodującego problem

Może się zdarzyć, że niezgodny kod nie jest już potrzebny lub można go przepisać bez funkcji powodujących naruszenia:

Tak
el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);
Nie
el.innerHTML = '<img src=xyz.jpg>';

Używanie biblioteki

Niektóre biblioteki generują już Trusted Types, które możesz przekazywać do funkcji ujścia. Możesz na przykład użyć DOMPurify aby oczyścić fragment kodu HTML i usunąć ładunki XSS.

import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});

DOMPurify obsługuje Trusted Types i zwraca oczyszczony kod HTML opakowany w obiekt TrustedHTML, dzięki czemu przeglądarka nie generuje naruszenia.

Tworzenie zasady Trusted Type

Czasami nie można usunąć kodu powodującego naruszenie, a nie ma biblioteki, która mogłaby oczyścić wartość i utworzyć dla Ciebie Trusted Type. W takich przypadkach możesz samodzielnie utworzyć obiekt Trusted Type.

Najpierw utwórz zasadę. Zasady to fabryki Trusted Types, które wymuszają określone reguły bezpieczeństwa na danych wejściowych:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
    createHTML: string => string.replace(/\</g, '&lt;')
  });
}

Ten kod tworzy zasadę o nazwie myEscapePolicy, która może tworzyć obiekty TrustedHTML za pomocą funkcji createHTML(). Zdefiniowane reguły powodują, że znaki < są zastępowane kodem HTML, aby uniemożliwić tworzenie nowych elementów HTML.

Użyj zasady w ten sposób:

const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML);  // true
el.innerHTML = escaped;  // '&lt;img src=x onerror=alert(1)>'

Używanie zasady domyślnej

Czasami nie można zmienić kodu powodującego problem, np. jeśli ładujesz bibliotekę innej firmy z CDN. W takim przypadku użyj a zasady domyślnej:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  trustedTypes.createPolicy('default', {
    createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
  });
}

Zasada o nazwie default jest używana wszędzie tam, gdzie w ujściu, które akceptuje tylko Trusted Type, używany jest ciąg znaków.

Przełączanie się na egzekwowanie standardu Content Security Policy

Gdy Twoja aplikacja nie będzie już generować naruszeń, możesz zacząć egzekwować Trusted Types:

Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

Teraz, niezależnie od tego, jak złożona jest Twoja aplikacja internetowa, jedyną rzeczą, która może wprowadzić lukę w zabezpieczeniach DOM XSS, jest kod w jednej z Twoich zasad. Możesz jeszcze bardziej ograniczyć ryzyko, ograniczając tworzenie zasad.

Więcej informacji