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.
- Manipulowanie skryptami:
<script src>
i określanie zawartości tekstowej elementów<script>
. - Generowanie kodu HTML na podstawie ciągu znaków:
- Uruchamianie treści wtyczki:
- Kompilacja kodu JavaScript środowiska wykonawczego:
eval
setTimeout
setInterval
new Function()
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:
anElement.innerHTML = location.href;
Aby zaznaczyć, że dane zostały przetworzone w bezpieczny sposób, utwórz specjalny obiekt – typ zaufany.
anElement.innerHTML = aTrustedHTML;
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:
el.textContent = ''; const img = document.createElement('img'); img.src = 'xyz.jpg'; el.appendChild(img);
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, '<')
});
}
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; // '<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.