Cross-site scripting (XSS) to podatność aplikacji webowej: atakujący wstrzykuje własny kod JavaScript do treści serwowanej innym użytkownikom. Przeglądarka wykonuje ten kod tak, jakby pochodził z zaufanej strony. Skutek to działania w imieniu ofiary: przejęcie sesji, kradzież danych z formularzy, podmiana treści lub przekierowanie na stronę phishingową w obrębie zaufanej domeny.
Stan prawny na dzień 2 września 2026 r.
- Na czym polega XSS — aplikacja umieszcza niezaufane dane w stronie bez kodowania, więc przeglądarka traktuje wstrzyknięty skrypt jak własny kod strony.
- Trzy odmiany — reflected (odbity z żądania), stored (zapisany na serwerze), DOM-based (powstający w kodzie po stronie przeglądarki); blind XSS to wariant stored uruchamiany poza widokiem atakującego.
- Główna obrona — kodowanie wyjścia zależne od kontekstu plus Content Security Policy z nonce; walidacja wejścia i flagi ciasteczek to warstwy uzupełniające.
- Jak sprawdzić własną aplikację — testy według OWASP WSTG z użyciem Burp Suite, ZAP i DOM Invadera, najlepiej w ramach pełnego testu penetracyjnego.
Poniższe przykłady kodu podano wyłącznie w celach edukacyjnych, aby pokazać mechanizm podatności i sposób obrony.
Czym jest cross-site scripting i dlaczego wciąż działa?
Cross-site scripting to podatność, w której aplikacja umieszcza dane od użytkownika na stronie bez odpowiedniego kodowania. Przeglądarka wykonuje wtedy wstrzyknięty skrypt zamiast potraktować go jako zwykły tekst. Przeglądarki chroni zasada tego samego pochodzenia (same-origin policy): blokuje ona skryptom z jednej domeny dostęp do danych z innej. XSS obchodzi tę barierę nie łamiąc jej, lecz wykorzystując zaufanie. Złośliwy skrypt wykonuje się w kontekście zaatakowanej domeny. Sięga więc po jej ciasteczka, tokeny i DOM tak samo jak legalny kod strony.
Podatność wciąż jest powszechna, bo powstaje w codziennej pracy z danymi, a nie w egzotycznych scenariuszach. Każde pole formularza, parametr adresu URL, nagłówek żądania czy rekord w bazie może stać się nośnikiem payloadu, jeśli aplikacja wyświetli go później bez kodowania. Nowoczesne frameworki ograniczyły klasyczne przypadki, ale jednocześnie pojawiły się nowe: dynamiczne renderowanie w aplikacjach jednostronicowych przeniosło część ryzyka z serwera do przeglądarki. Zestawienie OWASP Top 10 z 2021 r. umieszcza XSS w kategorii A03: Injection, a OWASP utrzymuje dla tej podatności dedykowany dokument prewencyjny.
Rozróżnijmy moment wstrzyknięcia od momentu wykonania. W jednej odmianie skrypt uruchamia się natychmiast po kliknięciu spreparowanego odnośnika. W innej najpierw trafia na serwer, a wykonuje się dopiero wtedy, gdy ofiara otworzy zainfekowaną stronę. To rozróżnienie decyduje o tym, kto padnie ofiarą i jak wykrywa się daną podatność. Powiązane ryzyka opisuje wpis czy strona może zostać zhakowana.
Jakie są trzy odmiany XSS?
Trzy podstawowe odmiany XSS różnią się miejscem, w którym payload żyje i się uruchamia. Reflected odbija się z bieżącego żądania, stored zapisuje się na serwerze, a DOM-based powstaje w całości w kodzie JavaScript po stronie przeglądarki. Blind XSS to wariant stored: atakujący nie widzi efektu, bo skrypt uruchamia się w interfejsie niedostępnym dla niego, na przykład w panelu administracyjnym.


Reflected XSS
Reflected XSS występuje, gdy aplikacja odbija dane z bieżącego żądania z powrotem w odpowiedzi bez kodowania, najczęściej przez parametr adresu URL lub pole wyszukiwania. Atakujący nakłania ofiarę do kliknięcia spreparowanego odnośnika, na przykład w wiadomości phishingowej, a skrypt wykonuje się w przeglądarce w kontekście zaufanej domeny. Payload nie jest nigdzie zapisywany, więc atak działa tylko na tego, kto otworzy przygotowany link.
Stored XSS
Stored XSS polega na trwałym zapisaniu payloadu na serwerze, skąd trafia on do każdego użytkownika wyświetlającego zainfekowaną treść. Klasyczne miejsca to systemy komentarzy, opinie, profile, wiadomości i pola formularzy renderowane później w panelu obsługi. Ta odmiana jest groźniejsza od reflected. Nie wymaga nakłaniania ofiary do kliknięcia: wystarczy, że otworzy stronę, która i tak jest częścią jej pracy. Przebieg takiego ataku opisuje studium przypadku: stored XSS w panelu CRM.
DOM-based XSS
DOM-based XSS powstaje w całości po stronie przeglądarki. Kod JavaScript aplikacji pobiera dane z niezaufanego źródła — na przykład z fragmentu adresu URL — i wstawia je do drzewa DOM przez niebezpieczną operację. Serwer może w ogóle nie zobaczyć payloadu, więc zabezpieczenia po stronie backendu tej odmiany nie wykryją. Ta odmiana jest częstym problemem w aplikacjach jednostronicowych opartych na frameworkach React, Angular czy Vue, gdzie przeglądarka renderuje treść dynamicznie.
Blind XSS jako wariant stored
Blind XSS to odmiana stored, w której skrypt uruchamia się w interfejsie niewidocznym dla atakującego, dlatego efektu nie da się zaobserwować bezpośrednio w miejscu wstrzyknięcia. Typowy scenariusz: payload trafia w formularzu kontaktowym do bazy, a wykonuje się dopiero, gdy pracownik otworzy zgłoszenie w wewnętrznym panelu. Do wykrywania tej odmiany używa się payloadów, które sygnalizują wykonanie na zewnętrzny serwer kontrolowany przez testera.
| Odmiana | Gdzie żyje payload | Kto jest ofiarą | Jak wykryć | Przykładowe miejsce |
|---|---|---|---|---|
| Reflected | W bieżącym żądaniu (URL, formularz), odbity w odpowiedzi | Użytkownik, który kliknął spreparowany odnośnik | Wstrzyknięcie markera w parametry i analiza odbicia w odpowiedzi | Pole wyszukiwania, komunikat błędu z echem parametru |
| Stored | Trwale na serwerze (baza, plik, cache) | Każdy, kto wyświetli zainfekowaną treść | Wstrzyknięcie w jednym widoku, weryfikacja wykonania w innym | Komentarze, opinie, profil, notatki w CRM |
| DOM-based | W przeglądarce, w drzewie DOM; serwer może go nie widzieć | Użytkownik aplikacji renderującej dane w JavaScript | Analiza przepływu danych od źródła do niebezpiecznej operacji (DOM Invader) | Aplikacje SPA, routing po fragmencie URL |
| Blind (wariant stored) | Trwale na serwerze, wykonanie w interfejsie ukrytym przed atakującym | Pracownik obsługujący zgłoszenia w panelu wewnętrznym | Payload z sygnalizacją wykonania na zewnętrzny serwer testera | Formularz kontaktowy renderowany w panelu obsługi |
Jak wygląda atak XSS krok po kroku?
Atak XSS przebiega w trzech etapach. Atakujący wstrzykuje payload w punkt wejścia, aplikacja umieszcza go w treści bez kodowania, a przeglądarka ofiary wykonuje go w kontekście zaufanej domeny. Poniższy przykład pokazuje mechanizm na najprostszym możliwym przypadku i rozdziela kod podatny od payloadu.
Załóżmy, że aplikacja wyświetla frazę wpisaną w wyszukiwarce i wstawia parametr wprost do strony, bez kodowania. Podatny fragment (PHP) wygląda tak:
<?php
// PODATNE: parametr trafia do HTML bez kodowania
echo "Wyniki dla: " . $_GET['q'];
?>
Jeśli w parametrze q znajdzie się nie tekst, lecz znaczniki HTML, przeglądarka potraktuje je jako kod. Payload demonstracyjny, który jedynie wyświetla nazwę domeny i potwierdza wykonanie skryptu, wygląda tak:
?q=<script>alert(document.domain)</script>
Zamiast tekstu przeglądarka wykona skrypt. W realnym ataku wywołanie alert nie ma znaczenia: liczy się fakt, że napastnik może uruchomić dowolny JavaScript w kontekście strony. Popularny wariant nie wymaga nawet znacznika <script>, bo wykorzystuje zdarzenie w innym elemencie:
<img src=x onerror=alert(document.domain)>
Kradzież sesji opisujemy tu bez działającego kodu. Wstrzyknięty skrypt odczytuje token sesyjny dostępny dla JavaScript i wysyła go na serwer atakującego. Może też wykonywać żądania w imieniu zalogowanej ofiary, bez wykradania samego ciasteczka. Dlatego sam alert w teście jest tylko sygnałem: kanał do wykonania kodu jest otwarty. Rzeczywisty wpływ zależy od tego, do czego ten kod uzyska dostęp.
Poprawka usuwa podatność u źródła, przez kodowanie danych przy wstawianiu ich do HTML:
<?php
// BEZPIECZNE: znaki specjalne zamienione na encje
echo "Wyniki dla: " . htmlspecialchars($_GET['q'], ENT_QUOTES, 'UTF-8');
?>
Po tej zmianie ciąg <script> zamienia się na nieszkodliwy tekst <script>, który przeglądarka wyświetli dosłownie zamiast wykonać.
Jakie są skutki XSS dla firmy?
Skutki XSS wykraczają poza pojedynczą stronę, bo skrypt działa z pełnymi uprawnieniami zaatakowanej domeny i może robić wszystko, co w tym kontekście robi legalny kod. Najczęstsze konsekwencje to przejęcie sesji, przechwytywanie danych wpisywanych przez użytkownika, phishing wewnątrz zaufanej domeny i podmiana treści. Napastnik wykorzystuje też sesję zalogowanego pracownika do ruchu bocznego w kierunku panelu administracyjnego.
Przejęcie sesji to najczęściej opisywany skutek: skrypt wykorzystuje sesję ofiary, aby działać na jej koncie, albo wyprowadza token uwierzytelniający. Keylogging oznacza tu rejestrowanie znaków wpisywanych w formularze na zainfekowanej stronie. Zapis obejmuje dane logowania i numery kart, jeszcze zanim trafią one do bezpiecznego przetwarzania.
Phishing wewnątrz zaufanej domeny jest szczególnie skuteczny, bo ofiara widzi prawdziwy adres i prawdziwy certyfikat. Wstrzyknięty skrypt może wyświetlić fałszywy formularz logowania na autentycznej stronie, przez co typowe wskazówki ostrzegawcze zawodzą. Defacement, czyli podmiana widocznej treści, uderza w wizerunek i zaufanie, a w sklepie internetowym może przekierować płatność do napastnika.
Najpoważniejszy scenariusz w środowisku firmowym to ruch boczny. Stored XSS zapisany przez zwykłego użytkownika może uruchomić się w przeglądarce administratora otwierającego zgłoszenie w panelu, dając napastnikowi dostęp do funkcji o wyższych uprawnieniach. Tak podatność w pozornie błahym polu formularza staje się punktem wejścia do systemu zarządzania. Skalę takiego ruchu bocznego pokazuje studium przypadku: stored XSS w panelu CRM.
Jak zabezpieczyć aplikację przed XSS?
Skuteczna obrona przed XSS opiera się na kodowaniu wyjścia zależnym od kontekstu, wzmocnionym przez Content Security Policy, a nie na pojedynczym filtrze wejścia. Poniższe warstwy działają razem: żadna samodzielnie nie eliminuje ryzyka, ale ich połączenie znacząco podnosi koszt ataku i ogranicza skutki ewentualnego błędu.

- Kodowanie wyjścia zależne od kontekstu — dane wstawiaj do strony zakodowane zgodnie z miejscem: inaczej w treści HTML, inaczej w atrybucie, inaczej w kodzie JavaScript czy adresie URL. To podstawowa i najważniejsza warstwa, opisana w OWASP Cross Site Scripting Prevention Cheat Sheet.
- Walidacja wejścia — odrzucaj dane niezgodne z oczekiwanym formatem (na przykład numer, który ma być liczbą). Walidacja ogranicza powierzchnię ataku, ale nie zastępuje kodowania, bo wiele poprawnych danych może zawierać znaki specjalne.
- Content Security Policy z nonce — polityka CSP ogranicza źródła wykonywanych skryptów; z jednorazowym tokenem (nonce) przeglądarka uruchomi tylko skrypty oznaczone bieżącym nonce, blokując wstrzyknięte znaczniki inline. Zasady CSP opisuje dokumentacja MDN.
- Flagi ciasteczek HttpOnly i SameSite —
HttpOnlyodcina JavaScript od ciasteczka sesyjnego, więc skrypt nie odczyta go bezpośrednio;SameSiteogranicza wysyłanie ciasteczka w żądaniach z innych witryn. To zmniejsza skutki, nie usuwa przyczyny. - Frameworki z automatycznym escapingiem i świadomość ich pułapek — React, Angular czy szablony Django domyślnie kodują wartości. Ochronę omijają jednak celowe wyjątki:
dangerouslySetInnerHTMLw Reakcie,v-htmlw Vue oraz bezpośrednie przypisanie doinnerHTML. Każde takie użycie to miejsce do osobnego przeglądu. - Trusted Types — mechanizm przeglądarkowy, który wymusza przepuszczanie danych trafiających do niebezpiecznych operacji DOM przez zdefiniowaną politykę, ograniczając DOM-based XSS u źródła.
- Testy jako stała część procesu — automatyczne i manualne testy bezpieczeństwa wbudowane w cykl wytwarzania wychwytują regresje, zanim trafią na produkcję.
Osobno odnotujmy nagłówek X-XSS-Protection, wciąż zalecany w starszych poradnikach. Nowoczesne przeglądarki go ignorują, a producenci wycofali wbudowane filtry XSS, bo same bywały źródłem podatności. Zamiast niego stosuj Content Security Policy. Konfigurację nagłówków bezpieczeństwa weryfikuje audyt bezpieczeństwa IT.
| Warstwa | Mechanizm | Co daje | Czego nie daje |
|---|---|---|---|
| Kodowanie wyjścia | Kodowanie zależne od kontekstu (HTML, atrybut, JS, URL) | Usuwa przyczynę XSS w miejscu wstawiania danych | Nie chroni, jeśli pominięto choć jeden kontekst wyjścia |
| Walidacja wejścia | Odrzucanie danych niezgodnych z formatem | Zmniejsza powierzchnię ataku | Nie zastępuje kodowania; poprawne dane też miewają znaki specjalne |
| Content Security Policy | Polityka źródeł skryptów z nonce | Blokuje wykonanie wstrzykniętych skryptów inline | Nie naprawia podatności; działa jako warstwa ograniczająca skutki |
| Flagi ciasteczek | HttpOnly i SameSite | Utrudnia kradzież sesji i żądania cross-site | Nie blokuje wykonania skryptu ani działań w imieniu ofiary |
| Framework z auto-escapingiem | Domyślne kodowanie wartości w szablonach | Chroni typowe przypadki bez dodatkowego kodu | Omijają go dangerouslySetInnerHTML, v-html, innerHTML |
| Trusted Types | Polityka dla operacji DOM | Ogranicza DOM-based XSS u źródła | Wymaga wsparcia przeglądarki i adaptacji kodu |
Jak testować aplikację pod kątem XSS?
Aplikację testuje się pod kątem XSS, łącząc metodykę z narzędziami. Przewodnik OWASP Web Security Testing Guide (WSTG) porządkuje, co i jak sprawdzić. Burp Suite, OWASP ZAP i DOM Invader pomagają wykryć poszczególne odmiany. Testy najlepiej prowadzić w ramach pełnego testu penetracyjnego, który weryfikuje nie tylko istnienie podatności, ale też jej realny wpływ na dane i uprawnienia.
OWASP WSTG opisuje osobne procedury dla reflected, stored i DOM-based XSS, dzięki czemu test obejmuje wszystkie punkty wejścia, a nie tylko te najbardziej oczywiste. Burp Suite pełni rolę proxy przechwytującego ruch i pozwala manualnie modyfikować żądania oraz obserwować odpowiedzi. OWASP ZAP oferuje zbliżone możliwości jako narzędzie open source, które łatwo włączyć do potoku CI/CD. DOM Invader, wbudowany w przeglądarkę Burpa, śledzi przepływ danych w DOM i wskazuje niebezpieczne operacje. To decyduje o wykryciu DOM-based XSS, którego po stronie serwera nie widać.
Narzędzia automatyczne wykrywają typowe przypadki, ale wiele odmian, zwłaszcza blind i DOM-based, wymaga analizy manualnej i zrozumienia logiki aplikacji. Dlatego skanowanie traktuje się jako uzupełnienie, a nie zamiennik testu prowadzonego przez specjalistę. Zakres takiego testu dla własnej aplikacji określisz w konfiguratorze zakresu. Przebieg testu opisuje wpis testy bezpieczeństwa IT krok po kroku.
XSS a wymogi regulacyjne
XSS adresują wprost uznane standardy bezpieczeństwa aplikacji, a pośrednio regulacje wymagające bezpiecznego wytwarzania oprogramowania. Odniesienie do tych ram pomaga uzasadnić nakłady na testy i kodowanie wyjścia jako element zgodności, a nie tylko dobrej praktyki technicznej.
Zestawienie OWASP Top 10 z 2021 roku umieszcza cross-site scripting w kategorii A03: Injection. Obok niego stoją inne podatności polegające na wstrzyknięciu danych, które system potem interpretuje. Zespoły powszechnie stosują to zestawienie jako punkt odniesienia przy definiowaniu zakresu testów bezpieczeństwa aplikacji webowych.
OWASP Application Security Verification Standard (ASVS) zawiera osobny rozdział o kodowaniu wyjścia i ochronie przed wstrzyknięciami. Rozdział ten daje zespołom deweloperskim i audytorom konkretne, weryfikowalne wymagania. ASVS pozwala przełożyć ogólną zasadę “koduj wyjście” na sprawdzalne kryteria akceptacji.
Na poziomie prawa unijnego obowiązuje dyrektywa Parlamentu Europejskiego i Rady (UE) 2022/2555 (NIS2). Dyrektywa w art. 21 ust. 2 lit. e wskazuje bezpieczeństwo nabywania, rozwoju i utrzymania systemów informatycznych jako jeden z minimalnych środków zarządzania ryzykiem. Podmioty objęte dyrektywą mieszczą w tym obowiązku testy pod kątem podatności takich jak XSS oraz kodowanie wyjścia wbudowane w proces wytwarzania. Spełnienie tych wymagań potwierdza audyt bezpieczeństwa IT.
Najczęstsze pytania o cross-site scripting (XSS)
Czym różni się reflected XSS od stored XSS?
Reflected XSS odbija payload z bieżącego żądania i działa tylko na użytkownika, który kliknął spreparowany odnośnik, bez zapisywania kodu na serwerze. Stored XSS zapisuje payload trwale na serwerze, więc uruchamia się u każdego, kto wyświetli zainfekowaną treść, bez potrzeby nakłaniania ofiary do kliknięcia. Stored jest zwykle groźniejszy, bo dociera do wielu użytkowników i może uderzyć w konta o wysokich uprawnieniach.
Czy Content Security Policy wystarczy do ochrony przed XSS?
Content Security Policy nie wystarcza samodzielnie, bo jest warstwą ograniczającą skutki, a nie naprawiającą przyczynę. CSP z nonce blokuje wykonanie wstrzykniętych skryptów inline, ale podstawą pozostaje kodowanie wyjścia zależne od kontekstu. Najlepszy efekt daje połączenie obu mechanizmów z flagami ciasteczek i testami, bo błąd w jednej warstwie nie oznacza wtedy całkowitej kompromitacji.
Czy nowoczesne frameworki eliminują XSS?
Frameworki takie jak React, Angular czy Vue kodują wartości domyślnie i usuwają większość klasycznych przypadków, ale nie eliminują ryzyka. Ochronę omijają celowe wyjątki: dangerouslySetInnerHTML, v-html oraz bezpośrednie przypisanie do innerHTML. Dodatkowo dynamiczne renderowanie w przeglądarce zwiększa ryzyko DOM-based XSS, dlatego aplikacje jednostronicowe wymagają osobnej uwagi podczas testów.
Czy nagłówek X-XSS-Protection nadal chroni aplikację?
Nagłówek X-XSS-Protection nie chroni już aplikacji, bo nowoczesne przeglądarki go ignorują, a wbudowane filtry XSS zostały wycofane jako źródło własnych podatności. Zamiast niego stosuje się Content Security Policy z nonce. Utrzymywanie tego nagłówka w konfiguracji nie szkodzi, ale nie należy traktować go jako realnego zabezpieczenia.
Co to jest blind XSS i dlaczego jest trudny do wykrycia?
Blind XSS to odmiana stored, w której skrypt uruchamia się w interfejsie niewidocznym dla atakującego, na przykład w wewnętrznym panelu obsługi zgłoszeń. Trudność w wykryciu wynika z tego, że w miejscu wstrzyknięcia nie widać żadnego efektu. Do testów używa się payloadów sygnalizujących wykonanie na zewnętrzny serwer kontrolowany przez testera, co pozwala potwierdzić, że kod się uruchomił.
Jak sprawdzić, czy moja aplikacja jest podatna na XSS?
Najpewniejszym sposobem jest test penetracyjny prowadzony według OWASP WSTG, łączący skanowanie automatyczne z analizą manualną wszystkich punktów wejścia. Narzędzia takie jak Burp Suite, OWASP ZAP i DOM Invader wspierają wykrywanie poszczególnych odmian, w tym DOM-based. Skanowanie samo w sobie nie wystarcza, bo blind i DOM-based XSS zwykle wymagają zrozumienia logiki aplikacji przez specjalistę.
Jak Pentestica może pomóc
Pentestica prowadzi testy penetracyjne aplikacji webowych i API, w których cross-site scripting jest jednym ze standardowo weryfikowanych obszarów zgodnie z OWASP WSTG i ASVS. Sprawdzamy wszystkie odmiany XSS, w tym stored i DOM-based w aplikacjach jednostronicowych. Potwierdzamy też realny wpływ podatności — na przykład możliwość ruchu bocznego do panelu administracyjnego — zamiast poprzestawać na sygnale ze skanera. Wnioski dostarczamy w formie raportu z priorytetami i konkretnymi zaleceniami dla zespołu deweloperskiego. Zakres testu dobierzesz na stronie testy penetracyjne i w konfiguratorze zakresu.
Źródła
- OWASP Cross Site Scripting Prevention Cheat Sheet — zasady kodowania wyjścia zależnego od kontekstu.
- OWASP Top 10 2021, A03: Injection — kategoria, do której włączono cross-site scripting.
- OWASP Web Security Testing Guide (WSTG) — metodyka testowania aplikacji, w tym procedury dla odmian XSS.
- OWASP Application Security Verification Standard (ASVS) — weryfikowalne wymagania dotyczące kodowania wyjścia i ochrony przed wstrzyknięciami.
- PortSwigger Web Security Academy — Cross-site scripting — opis odmian XSS i technik testowania.
- MDN — Content Security Policy (CSP) — dokumentacja mechanizmu CSP i dyrektyw źródeł skryptów.
- Dyrektywa (UE) 2022/2555 (NIS2) — art. 21 ust. 2 lit. e o bezpieczeństwie nabywania, rozwoju i utrzymania systemów.

Redakcja Pentestica to zespół pentesterów i konsultantów bezpieczeństwa, którzy na co dzień wykonują testy penetracyjne, testy TLPT wymagane przez DORA, red teaming i audyty bezpieczeństwa IT, a także prowadzą wdrożenia zgodności z NIS2, DORA i MiCA. Teksty na tym blogu piszemy z tej praktyki, a nie z opracowań. Każde twierdzenie o przepisie, terminie albo karze opieramy na źródle pierwotnym: dzienniku ustaw, tekście dyrektywy, komunikacie organu nadzoru. Jeśli czegoś nie da się potwierdzić, piszemy o tym wprost, zamiast powtarzać liczbę krążącą po branżowych portalach.