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.
- Manipulowanie skryptami:
<script src>i ustawianie zawartości tekstowej<script>elementów. - Generowanie kodu HTML z ciągu znaków:
- Wykonywanie treści wtyczek:
- Kompilowanie kodu JavaScript w czasie działania:
evalsetTimeoutsetIntervalnew Function()
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:
anElement.innerHTML = location.href;
Aby wskazać, że dane zostały bezpiecznie przetworzone, utwórz specjalny obiekt – Trusted Type.
anElement.innerHTML = aTrustedHTML;
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:
el.textContent = ''; const img = document.createElement('img'); img.src = 'xyz.jpg'; el.appendChild(img);
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, '<')
});
}
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; // '<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.