Skryptowanie między witrynami oparte na DOM (DOM XSS) występuje, gdy dane ze źródła kontrolowanego przez użytkownika (np. nazwy użytkownika lub adresu URL przekierowania pobranego z fragmentu adresu URL) docierają do sinka, czyli funkcji takiej jak eval()
lub settera właściwości takiej jak .innerHTML
, który może wykonać dowolny kod JavaScript.
Atak XSS w DOM jest jedną z najczęstszych luk w zabezpieczeniach witryn internetowych i często jest przypadkowo wprowadzany przez zespoły programistów do aplikacji. Zaufane typy to narzędzia do pisania i sprawdzania zabezpieczeń oraz do ochrony aplikacji przed podatnością na ataki XSS w DOM, które są domyślnie zabezpieczone dzięki niebezpiecznym funkcjom interfejsu API. Zaufane typy są dostępne jako polyfill w przeglądarkach, które jeszcze ich nie obsługują.
Tło
Przez wiele lat XSS w DOM był jedną z najczęstszych i najniebezpieczniejszych luk w zabezpieczeniach sieci.
Istnieją 2 rodzaje ataków typu cross-site scripting. 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ą pierwotną przyczynę po stronie klienta, gdzie kod JavaScript wywołuje niebezpieczne funkcje z zawartością kontrolowaną 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 oraz zasady Content Security Policy, które umożliwiają dodatkowe łagodzenie błędów.
Teraz przeglądarki mogą też zapobiegać atakom XSS po stronie klienta na podstawie DOM-u, korzystając z zaufanych typów.
Wprowadzenie do interfejsów API
Zaufane typy działają, blokując poniższe ryzykowne funkcje ujścia. Niektóre z nich możesz już znać, ponieważ producenci przeglądarek i ramy sieciowe odradzają używania tych funkcji ze względów bezpieczeństwa.
- Manipulowanie skryptami:
<script src>
i ustawianie treści tekstowej elementów<script>
. - Generowanie kodu HTML z ciągu znaków:
- Wykonywanie treści w pluginie:
- Kompilacja kodu JavaScript środowiska wykonawczego:
eval
setTimeout
setInterval
new Function()
Zaufane typy wymagają przetworzenia danych przed przekazaniem ich do tych funkcji zlewowych. Użycie tylko ciągu tekstowego spowoduje niepowodzenie, ponieważ przeglądarka nie wie, czy dane są wiarygodne:
anElement.innerHTML = location.href;
Aby wskazać, że dane zostały przetworzone bezpiecznie, utwórz specjalny obiekt – zaufany typ.
anElement.innerHTML = aTrustedHTML;
Zaufane typy znacznie zmniejszają obszar, na którym można przeprowadzić atak XSS w DOM-ie, w 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 zgłaszania naruszeń 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ż dodać niestandardowe logowanie i debugowanie naruszeń w przeglądarce za pomocą klasy 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 są zgłaszane do //my-csp-endpoint.example
, ale strona nadal działa. W następnej sekcji wyjaśniamy, jak działa //my-csp-endpoint.example
.
Identyfikowanie naruszeń zasad dotyczących zaufanych typów
Od tej pory za każdym razem, gdy Trusted Types wykryje naruszenie, przeglądarka będzie wysyłać raport do skonfigurowanego 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"
}
}
Z tego wynika, że w pliku https://my.url.example/script.js
na linii 39 funkcja innerHTML
została wywołana z ciągiem znaków zaczynającym się od <img src=x
. Te informacje powinny pomóc Ci określić, które części kodu mogą powodować atak XSS w DOM i wymagają zmiany.
Napraw naruszenia
Naruszenie zasad dotyczących zaufanego typu można naprawić na kilka sposobów. Możesz usunąć naruszający kod, skorzystać z biblioteki, utworzyć zasadę zaufanego typu lub utworzyć zasadę domyślną.
Zmień kod powodujący problem.
Możliwe, że kod niezgodny z zasadami nie jest już potrzebny lub można go przepisać bez funkcji, które powodują naruszenia:
el.textContent = ''; const img = document.createElement('img'); img.src = 'xyz.jpg'; el.appendChild(img);
el.innerHTML = '<img src=xyz.jpg>';
Korzystanie z biblioteki
Niektóre biblioteki generują już zaufane typy, które możesz przekazywać do funkcji sink. Możesz na przykład użyć narzędzia DOMPurify do odizolowania fragmentu kodu HTML i usunięcia z niego ładunków 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.
Tworzenie zasad dotyczących zaufanych typów
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 zaufany.
Najpierw utwórz zasady. Zasady to fabryki zaufanych typów, które stosują 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, '<')
});
}
Ten kod tworzy zasadę o nazwie myEscapePolicy
, która może generować obiekty TrustedHTML
za pomocą funkcji createHTML()
. Zdefiniowane reguły HTML-escape <
znaków, aby zapobiec tworzeniu nowych elementów HTML.
Użyj tej zasady:
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żyj zasady domyślnej
Czasami nie możesz zmienić kodu, który powoduje problemy, na przykład wtedy, gdy wczytujesz bibliotekę zewnętrzną z CDN. W takim przypadku użyj zasad domyślnych:
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 zbiorniku jest używany ciąg znaków, który akceptuje tylko zaufany typ.
Przełącz na egzekwowanie zasad Content Security Policy
Gdy aplikacja przestanie generować naruszenia, możesz zacząć egzekwować zasady dotyczące zaufanych typów:
Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
Niezależnie od tego, jak złożona jest Twoja aplikacja internetowa, jedyną rzeczą, która może spowodować podatność na atak XSS w DOM, jest kod w jednej z zasad. Możesz jeszcze bardziej zablokować tę funkcję, ograniczając tworzenie zasad.