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

Krzysztof Kotowicz
Krzysztof Kotowicz

Obsługa przeglądarek

  • 83
  • 83
  • x
  • x

Źródło

Skrypty między witrynami oparte na modelu DOM (DOM XSS) mają miejsce, gdy dane ze źródła kontrolowanego przez użytkownika (np. nazwa użytkownika lub adres URL przekierowania pobrane z fragmentu adresu URL) dotrą do ujścia, czyli funkcji takiej jak eval() lub narzędzia do ustawiania właściwości, np. .innerHTML, które mogą wykonywać dowolny kod JavaScript.

DOM XSS to jedna z najczęstszych luk w zabezpieczeniach internetowych, a zespoły programistów często przypadkowo wprowadzają ją w swoich aplikacjach. Zaufane typy zapewniają narzędzia do pisania, weryfikacji zabezpieczeń i zabezpieczania aplikacji przed lukami w zabezpieczeniach DOM XSS dzięki domyślnemu zabezpieczaniu niebezpiecznych funkcji internetowych interfejsów API. Zaufane typy są dostępne jako polyfill w przeglądarkach, które jeszcze ich nie obsługują.

Wprowadzenie

Od wielu lat model DOM XSS należy do najbardziej powszechnych i niebezpiecznych luk w zabezpieczeniach.

Wyróżniamy 2 rodzaje skryptów między witrynami. Niektóre luki XSS są powodowane przez kod po stronie serwera, który w niebezpieczny sposób tworzy kod HTML tworzący witrynę. Inne mają główną przyczynę po stronie klienta – 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. Użyj zamiast tego bezpiecznych bibliotek tworzenia szablonów z automatycznym stosowaniem kontekstu, a także zasad bezpieczeństwa treści nieopartych na identyfikatorach, aby dodatkowo zmniejszyć liczbę błędów.

Teraz przeglądarki mogą też zapobiegać atakom XSS po stronie klienta opartym na DOM, korzystając z zaufanych typów.

Wprowadzenie do interfejsu API

Zaufane typy działają, blokując poniższe ryzykowne funkcje ujścia. Niektóre z nich być może już znasz, ponieważ dostawcy przeglądarek i platformy internetowe zniechęcają Cię do korzystania z tych funkcji ze względów bezpieczeństwa.

Zaufane typy wymagają przetworzenia danych przed przekazaniem ich do tych funkcji ujścia. Użycie tylko ciągu znaków kończy się niepowodzeniem, ponieważ przeglądarka nie wie, czy dane są wiarygodne:

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

Aby zasygnalizować, że dane zostały przetworzone w bezpieczny sposób, utwórz specjalny obiekt – zaufany typ.

Tak
anElement.innerHTML = aTrustedHTML;
  
Gdy włączone są zaufane typy, przeglądarka akceptuje obiekt TrustedHTML w przypadku ujść, które oczekują fragmentów HTML. Istnieją też obiekty TrustedScript i TrustedScriptURL dla innych wrażliwych ujść.

Zaufane typy znacznie zmniejszają powierzchnię ataku DOM XSS Twojej aplikacji. Upraszcza ona kontrole zabezpieczeń i umożliwia wymuszanie kontroli bezpieczeństwa zależnych od typu, które są przeprowadzane podczas kompilowania, lintowania lub łączenia kodu w czasie działania w przeglądarce.

Jak korzystać z Zaufanych typów

Przygotowanie do raportów o naruszeniu zasad Content Security Policy

Możesz wdrożyć kolektor raportów, taki jak reporting-api-processor lub go-csp-collector, albo użyć jednego z jego komercyjnych odpowiedników. Możesz też dodawać niestandardowe logowanie i debugować naruszenia w przeglądarce za pomocą narzędzia 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 odbiornik:

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

Dodaj nagłówek CSP tylko do raportowania

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

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

Teraz wszystkie naruszenia zostały zgłoszone //my-csp-endpoint.example, ale witryna nadal działa. W następnej sekcji wyjaśniamy, jak działa //my-csp-endpoint.example.

Zidentyfikuj naruszenia zasad zaufanych typów

Od teraz za każdym razem, gdy zaufane typy wykryją naruszenie, przeglądarka wysyła raport do skonfigurowanej report-uri. Jeśli na przykład aplikacja przekazuje ciąg znaków do funkcji innerHTML, przeglądarka wysyła taki 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 https://my.url.example/script.js w wierszu 39 funkcja innerHTML została wywołana z ciągiem zaczynającym się od <img src=x. Te informacje pomogą Ci określić, które części kodu zawierają DOM XSS i które wymagają zmian.

Naprawianie naruszeń

Naruszenie zasad dotyczących zaufanego typu możesz rozwiązać na kilka sposobów. Możesz usunąć naruszający kod, skorzystać z biblioteki, utworzyć zasadę zaufanego typu lub utworzyć zasadę domyślną.

Ponownie napisz kod naruszający zasady

Kod, który nie jest zgodny z zasadami, może nie być 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 wolno
el.innerHTML = '<img src=xyz.jpg>';

Korzystanie z biblioteki

Niektóre biblioteki generują już zaufane typy, które można przekazać do funkcji ujścia. Możesz na przykład użyć narzędzia 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 zaufane typy i zwraca oczyszczony kod HTML opakowany w obiekcie TrustedHTML, dzięki czemu przeglądarka nie wygeneruje naruszenia.

Utwórz zasadę zaufanego typu

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

Najpierw utwórz zasadę. Zasady to fabryki zaufanych typów, które egzekwują określone reguły zabezpieczeń na swoich 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 generować obiekty TrustedHTML za pomocą funkcji createHTML(). Zdefiniowane reguły HTML-uciekają < znaków, by uniemożliwić tworzenie nowych elementów HTML.

Użyj tych zasad:

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żyj zasady domyślnej

Czasami nie da się zmienić niewłaściwego kodu – na przykład wtedy, gdy wczytujesz 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})
  });
}

Zasada o nazwie default jest używana zawsze, gdy ciąg znaków jest używany w ujściu, który akceptuje tylko zaufany typ.

Przełącz na egzekwowanie zasad Content Security Policy

Gdy aplikacja nie powoduje już naruszeń, możesz zacząć egzekwować zaufane typy:

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

Bez względu na to, jak złożona jest Twoja aplikacja internetowa, jedyną rzeczą, która może spowodować lukę w zabezpieczeniach DOM XSS, jest kod w jednej z zasad. Możesz to jeszcze bardziej zablokować, ograniczając możliwość tworzenia zasad.

Więcej informacji