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: behind a flag.
  • Safari: 26.

Source

Skryptowanie na wielu stronach oparte na DOM (DOM XSS) występuje, gdy dane z ź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 ustawiacza właściwości takiego jak .innerHTML, który może wykonywać dowolny kod JavaScript.

DOM XSS to jedna z najczęstszych luk w zabezpieczeniach internetowych, która często jest nieumyślnie wprowadzana do aplikacji przez zespoły programistów. Trusted Types zapewniają narzędzia do pisania, sprawdzania pod kątem bezpieczeństwa i utrzymywania aplikacji wolnych od luk w zabezpieczeniach DOM XSS, ponieważ domyślnie zabezpieczają niebezpieczne funkcje interfejsu Web API. Trusted Types są dostępne jako polyfill w przypadku przeglądarek, które jeszcze ich nie obsługują.

Tło

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

Istnieją 2 rodzaje skryptów cross-site scripting. Niektóre luki w zabezpieczeniach XSS są spowodowane przez kod po stronie serwera, który w niebezpieczny sposób tworzy kod HTML stanowiący witrynę. Inne mają przyczynę główną po stronie klienta, gdzie kod JavaScript wywołuje niebezpieczne funkcje z treściami kontrolowanymi 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 unikaniem znaków specjalnych oraz zasad CSP opartych na jednorazowym kodzie, aby dodatkowo ograniczyć liczbę błędów.

Przeglądarki mogą teraz też pomagać w zapobieganiu atakom XSS opartym na DOM po stronie klienta, korzystając z dyrektywy Trusted Types.

Wprowadzenie do interfejsu API

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

Zaufane typy wymagają przetworzenia danych przed przekazaniem ich do tych funkcji ujścia. Użycie tylko ciągu tekstowego nie działa, ponieważ przeglądarka nie wie, czy dane są wiarygodne:

Nie
anElement.innerHTML  = location.href;
Gdy dyrektywa Trusted Types jest włączona, przeglądarka zgłasza TypeError i uniemożliwia użycie elementu DOM XSS z ciągiem znaków.

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

Tak
anElement.innerHTML = aTrustedHTML;
  
Gdy włączone są zaufane typy, przeglądarka akceptuje obiekt TrustedHTML w przypadku elementów docelowych, które oczekują fragmentów kodu HTML. Istnieją też obiekty TrustedScriptTrustedScriptURL dla innych wrażliwych miejsc docelowych.

Dyrektywa Trusted Types znacznie zmniejsza powierzchnię ataku typu DOM XSS w Twojej aplikacji. Upraszcza to sprawdzanie zabezpieczeń i umożliwia egzekwowanie w przeglądarce w czasie działania programu sprawdzania zabezpieczeń opartych na typach, które są wykonywane podczas kompilowania, lintowania lub łączenia kodu w pakiety.

Jak korzystać z zaufanych typów

Przygotowywanie raportów 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 na licencji open source), albo użyć jednego z odpowiedników komercyjnych. Możesz też dodać logowanie niestandardowe i debugować naruszenia w przeglądarce za pomocą interfejsu 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

Obecnie 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ń dotyczących zaufanych typów

Od tej pory za każdym razem, gdy zaufane typy wykryją naruszenie, przeglądarka wysyła raport do skonfigurowanego report-uri. Jeśli na przykład 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"
}
}

Oznacza to, że w pliku https://my.url.example/script.js w wierszu 39 wywołano funkcję 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ń

Naruszenie zasad dotyczących zaufanych typów można usunąć na kilka sposobów. Możesz usunąć problematyczny kod, użyć biblioteki, utworzyć zasadę zaufanych typów lub, w ostateczności, utworzyć zasadę domyślną.

Zmodyfikuj kod naruszający zasady

Możliwe, że niezgodny kod nie jest już potrzebny lub można go przepisać bez funkcji, które powodują naruszenia:

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

Korzystanie z biblioteki

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

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

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

Tworzenie zasady Trusted Types

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

Najpierw utwórz zasady. Zasady to fabryki zaufanych typów, które wymuszają określone reguły zabezpieczeń 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 stosują do znaków < kodowanie HTML, aby zapobiec tworzeniu nowych elementów HTML.

Zasady te możesz stosować 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żesz zmienić kodu, który narusza zasady, np. jeśli ładujesz bibliotekę innej firmy z sieci CDN. W takim przypadku użyj zasady domyślnej:

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

Zasady o nazwie default są używane wszędzie tam, gdzie w miejscu docelowym, które akceptuje tylko typ zaufany, używany jest ciąg znaków.

Przełączanie na egzekwowanie standardu Content Security Policy

Gdy aplikacja przestanie generować naruszenia, możesz zacząć egzekwować stosowanie zaufanych typów:

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ć to ryzyko, ograniczając tworzenie zasad.

Więcej informacji