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 krzyżowe oparte na DOM (DOM XSS) mają miejsce, gdy dane ze źródła kontrolowanego przez użytkownika (np. nazwa użytkownika lub przekierowanie pobrane z fragmentu adresu URL) docierają do ujścia, które jest funkcją taką jak eval() lub wskaźnikiem właściwości, takim jak .innerHTML, który może 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. Typy zaufane udostępniają narzędzia do pisania i sprawdzania bezpieczeństwa oraz zabezpieczania aplikacji przed lukami w zabezpieczeniach DOM XSS przez domyślne zabezpieczenia niebezpiecznych funkcji internetowych interfejsów API. Zaufane typy są dostępne jako polyfill w przeglądarkach, które jeszcze ich nie obsługują.

Wprowadzenie

Przez wiele lat model DOM XSS należy do najbardziej powszechnych i niebezpiecznych luk w zabezpieczeniach internetowych.

Istnieją dwa rodzaje skryptów cross-site scripting. Niektóre luki w zabezpieczeniach XSS są powodowane przez kod po stronie serwera, który niebezpiecznie tworzy kod HTML tworzący witrynę. Inną przyczyną jest przyczyna po stronie klienta – kod JavaScript wywołuje niebezpieczne funkcje z treściami kontrolowanymi przez użytkownika.

Aby zapobiegać atakom XSS po stronie serwera, nie generujej kodu HTML przez łączenie ciągów znaków. Aby dodatkowo ograniczyć błędy, użyj bezpiecznego, automatycznego określania znaczenia bibliotek szablonów w połączeniu z zasadami bezpieczeństwa treści niezależnymi od kontekstu.

Teraz przeglądarki mogą również zapobiegać sytuacjom XSS po stronie klienta za pomocą zaufanych typów.

Wprowadzenie do interfejsu API

Zaufane typy działają, blokując następujące ryzykowne funkcje ujścia. Być może już znasz niektóre z nich, ponieważ dostawcy przeglądarek i platformy internetowe odradzają korzystanie z nich ze względów bezpieczeństwa.

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

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

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

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

Zaufane typy znacznie zmniejszają powierzchnię ataku DOM XSS Twojej aplikacji. Upraszcza to sprawdzanie zabezpieczeń i pozwala egzekwować kontrole zabezpieczeń na podstawie typu wykonywane podczas kompilowania, lintowania lub grupowania kodu w czasie działania w przeglądarce.

Jak korzystać z zaufanych typów

Przygotowanie do raportów o naruszeniach zasad Content Security Policy

Możesz wdrożyć kolektor raportów, np. go-csp-collector, lub użyć jednego z jego komercyjnych odpowiedników. Przypadki naruszenia możesz też debugować w przeglądarce:

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 są zgłaszane firmie //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 dotyczących zaufanych typów

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

Naprawianie naruszeń

Istnieje kilka sposobów naprawy naruszenia typu Zaufany typ. Możesz usunąć nieprawidłowy kod, użyć biblioteki, utworzyć zasadę zaufanego typu lub utworzyć zasadę domyślną.

Przeredaguj kod naruszający zasady

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

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

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ć metody 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 obiekt TrustedHTML, dzięki czemu przeglądarka nie wygeneruje naruszenia.

Tworzenie zasady zaufanego typu

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

Najpierw utwórz zasady. Zasady to fabryki zaufanych typów, które wymuszają na danych wejściowych określone reguły zabezpieczeń:

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 przy użyciu swojej funkcji createHTML(). Zdefiniowane reguły zawierają znaki < zmieniające znaczenie kodu HTML, co uniemożliwia tworzenie nowych elementów HTML.

Zastosuj taką 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)>'
createPolicy()

Użyj zasady domyślnej

Czasami nie można zmienić nieprawidłowego kodu, np. 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 wszędzie tam, gdzie ciąg znaków jest używany w ujściu, które akceptuje tylko typ zaufany.

Przełącz na wymuszanie Content Security Policy

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

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, lukę w zabezpieczeniach DOM XSS może umieścić tylko kod w jednej z zasad. Możesz ją jeszcze bardziej zablokować, ograniczając tworzenie zasad.

Więcej informacji