Przejdź do treści

~/cyberbezpieczenstwo cat cross-site-scripting-jak-mozna-z….md

Cross-site scripting (XSS): rodzaje ataków i jak zabezpieczyć stronę

Czym jest atak XSS (Cross-site scripting), czym różni się XSS odbity, trwały i DOM, jakie ma skutki i jak chronić stronę: escaping, CSP, HttpOnly, WordPress.

CZCzarek Zawolski--aktualizacja=--czas=7 min--dział=Cyberbezpieczeństwo
Ilustracja wstrzyknięcia złośliwego skryptu do strony internetowej
tldr.txt — W skrócie

~ xad tldr cross-site-scripting-jak-m…

  • XSS to podatność, przez którą strona wyświetla dane od użytkownika jako kod — skrypt atakującego wykonuje się w przeglądarkach odwiedzających.
  • Trzy główne rodzaje: odbity (z linku lub formularza), trwały (zapisany np. w komentarzu) i DOM XSS (błąd w JavaScripcie po stronie przeglądarki).
  • Skutki to m.in. przejęcie sesji, działania w imieniu ofiary, podmiana treści i wyłudzanie danych na zaufanej domenie.
  • Podstawowa ochrona to kodowanie danych przy wyświetlaniu, zgodne z kontekstem (HTML, atrybut, URL, JavaScript), oraz unikanie innerHTML.
  • Dodatkowe warstwy: nagłówek Content-Security-Policy, ciasteczka HttpOnly, aktualne wtyczki i WAF — ale żadna z nich nie zastąpi poprawnego kodu.
$ tree --spis-tresci

Cross-site scripting (XSS) to podatność aplikacji webowej, przez którą strona wyświetla dane przesłane przez użytkownika jako kod, a nie jako tekst. Skrypt atakującego wykonuje się wtedy w przeglądarce każdej osoby, która otworzy podatną stronę — z uprawnieniami tej strony, a więc z dostępem do sesji, formularzy i treści widocznych dla zalogowanego użytkownika.

XSS jest jedną z najczęstszych podatności w aplikacjach webowych: w zestawieniu najgroźniejszych słabości oprogramowania CWE Top 25 z 2024 r. zajął pierwsze miejsce. Dobra wiadomość jest taka, że skutecznie zapobiegają mu konsekwentne praktyki programistyczne. Poniżej wyjaśniam, jak działa XSS, jakie ma odmiany i skutki oraz jak zabezpieczyć stronę — w czystym PHP, JavaScripcie i w WordPressie.

Jak działa cross-site scripting

Przeglądarka ufa kodowi, który przychodzi z danej domeny. Jeśli strona sklep.pl wstawia do HTML-a tekst wpisany przez użytkownika (zapytanie w wyszukiwarce, komentarz, nazwę profilu) bez zakodowania znaków specjalnych, to tekst zawierający znaczniki HTML lub JavaScript stanie się częścią strony.

Cross-site scripting działa poprzez wstrzyknięcie złośliwego kodu na stronę internetową

Klasyczny przykład podatnego kodu w PHP:

<?php
// PODATNE: zapytanie z adresu trafia do HTML bez kodowania
echo "<p>Wyniki wyszukiwania dla: " . $_GET['q'] . "</p>";

Jeśli parametr q zawiera znacznik HTML, przeglądarka go zinterpretuje. Poprawna wersja zamienia znaki specjalne (<, >, ", ', &) na encje HTML, więc wszystko wyświetla się jako zwykły tekst:

<?php
$q = $_GET['q'] ?? '';
echo '<p>Wyniki wyszukiwania dla: ' . htmlspecialchars($q, ENT_QUOTES | ENT_HTML5, 'UTF-8') . '</p>';

Istota problemu: dane od użytkownika zmieniają się w kod. Ochrona polega na tym, by w każdym miejscu, gdzie dane trafiają do HTML-a, JavaScriptu czy URL-a, pozostały danymi.

Rodzaje ataków XSS

RodzajGdzie jest złośliwy kodJak trafia do ofiary
Odbity (reflected)w żądaniu: parametrze URL, polu formularzaofiara musi kliknąć spreparowany link, np. z maila lub komunikatora
Trwały (stored)zapisany na serwerze: komentarz, opis produktu, nazwa użytkownika, zgłoszenie do supportuwykonuje się u każdego, kto wyświetli dane — także u administratora w panelu
DOM XSSw kodzie JavaScript strony, który niebezpiecznie przetwarza dane z adresu, postMessage czy localStorageczęsto nie przechodzi przez serwer, więc logi i WAF go nie widzą

XSS odbity

Najczęściej spotykany w wyszukiwarkach, komunikatach błędów i stronach, które powtarzają w treści parametry z adresu. Wymaga socjotechniki — ofiara musi wejść w przygotowany link, który prowadzi na prawdziwą, zaufaną domenę. To właśnie czyni go groźnym w phishingu.

XSS trwały

Najgroźniejsza odmiana, bo nie wymaga od ofiary żadnej akcji poza odwiedzeniem strony. Szczególnie niebezpieczny jest trwały XSS w miejscach oglądanych przez administratorów (zgłoszenia, zamówienia, logi w panelu): skrypt wykonany w sesji administratora może zmienić ustawienia, utworzyć konto lub zainstalować wtyczkę.

DOM XSS

Pojawia się, gdy kod front-endu bierze dane z niezaufanego źródła (np. location.hash) i wstawia je do strony funkcją interpretującą HTML. Typowy błąd i jego poprawka:

// PODATNE: tekst z adresu interpretowany jako HTML
document.getElementById('powitanie').innerHTML = decodeURIComponent(location.hash.slice(1));

// BEZPIECZNE: tekst wstawiany jako tekst
document.getElementById('powitanie').textContent = decodeURIComponent(location.hash.slice(1));

Niebezpieczne „ujścia” (sinki) w JavaScripcie to m.in. innerHTML, outerHTML, document.write(), insertAdjacentHTML(), eval(), setTimeout() z tekstem oraz atrybuty href/src ustawiane z niezweryfikowanych danych. We frameworkach odpowiednikami są dangerouslySetInnerHTML (React) i v-html (Vue).

Skutki ataku XSS

Skrypt działa w kontekście podatnej strony, więc może zrobić wszystko, co zalogowany użytkownik:

  • Przejęcie sesji — odczyt tokenów i ciasteczek dostępnych dla JavaScriptu (jeśli nie mają flagi HttpOnly). Mechanizm i obronę opisuje tekst o ataku session hijacking.
  • Działania w imieniu ofiary — zmiana adresu e-mail czy hasła, wysłanie wiadomości, złożenie zamówienia, a u administratora — zmiana konfiguracji serwisu.
  • Kradzież danych z ekranu — wszystko, co strona wyświetla zalogowanemu użytkownikowi.
  • Phishing na zaufanej domenie — fałszywy formularz logowania wyświetlony na prawdziwym adresie, z poprawnym certyfikatem.
  • Podmiana treści i reputacja — wstawione reklamy, przekierowania, spam SEO.

Tokeny trzymane w localStorage są w pełni dostępne dla skryptu, więc przy XSS wyciekają zawsze — o ograniczeniach tego mechanizmu piszę w artykule czym jest Local Storage.

Jak zabezpieczyć stronę przed XSS

Zabezpieczenie strony przed atakiem XSS

1. Koduj dane przy wyświetlaniu, zgodnie z kontekstem

To najważniejsza zasada. Ten sam ciąg znaków wymaga innego kodowania w zależności od tego, gdzie trafia:

KontekstPrzykładCo zastosować
Treść HTML<p>DANE</p>kodowanie encji HTML
Atrybut HTML<input value="DANE">kodowanie atrybutu, zawsze atrybut w cudzysłowie
Adres URL<a href="DANE">walidacja schematu (https:), kodowanie URL; blokada javascript:
JavaScript<script>var x = DANE</script>najlepiej unikać; dane przekazuj przez atrybut data- lub JSON z poprawnym kodowaniem
CSSstyle="DANE"unikać danych od użytkownika

Walidacja wejścia (np. „pole wiek przyjmuje tylko cyfry”) jest przydatna, ale nie wystarczy. Kodowanie wyjścia to właściwa obrona.

2. Korzystaj z automatycznego escapingu szablonów

Nowoczesne systemy szablonów (Twig, Blade, Jinja2, Razor) i frameworki front-endowe (React, Vue, Angular, Svelte) domyślnie kodują wstawiane wartości. Problemy zaczynają się, gdy programista to świadomie wyłącza: {!! !!} w Blade, |raw w Twigu, dangerouslySetInnerHTML w React. Każde takie miejsce powinno przejść przegląd kodu.

3. Oczyszczaj HTML, jeśli musisz go dopuścić

Gdy użytkownicy mogą formatować tekst (edytor WYSIWYG, opisy produktów), nie filtruj HTML-a własnymi wyrażeniami regularnymi — to przegrana walka. Użyj sprawdzonej biblioteki z listą dozwolonych znaczników, np. DOMPurify w przeglądarce lub HTML Purifier w PHP.

4. Wdróż Content Security Policy

Nagłówek CSP mówi przeglądarce, skąd wolno ładować i wykonywać skrypty. Dobrze ustawiony blokuje skrypty wstrzyknięte w treść, nawet jeśli gdzieś zostanie błąd. Przykład restrykcyjnej polityki opartej na nonce:

Content-Security-Policy: default-src 'self'; script-src 'nonce-LOSOWA_WARTOSC' 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'self'

Wartość nonce musi być losowa i inna przy każdym wygenerowaniu strony, a każdy legalny znacznik <script> dostaje atrybut nonce. Wdrożenie zacznij od nagłówka Content-Security-Policy-Report-Only, żeby zobaczyć, co by się zepsuło. Unikaj 'unsafe-inline' w script-src — przekreśla on większość ochrony.

Wskazówka: Stary nagłówek X-XSS-Protection jest przestarzały i nowoczesne przeglądarki go ignorują. Nie traktuj go jako zabezpieczenia — zamiast tego wdroż CSP.

5. Zabezpiecz ciasteczka i sesję

  • Ciasteczka sesyjne z flagami HttpOnly (niedostępne dla JavaScriptu), Secure i SameSite=Lax lub Strict.
  • Ponowne uwierzytelnienie przy krytycznych operacjach (zmiana hasła, e-maila, danych płatności).
  • Krótki czas życia sesji w panelach administracyjnych.

HttpOnly nie zapobiega XSS, ale ogranicza jego skutki: skrypt nie odczyta ciasteczka, choć nadal może wykonywać żądania w ramach otwartej strony.

6. Dodaj WAF jako dodatkową warstwę

Zapora aplikacji webowych zablokuje wiele typowych prób ataku i da czas na wydanie poprawki. Nie wykryje jednak wszystkiego, a DOM XSS często w ogóle do serwera nie dociera. Więcej w tekście co to jest WAF.

XSS w WordPressie

W ekosystemie WordPressa XSS to jeden z najczęściej zgłaszanych typów podatności — prawie zawsze we wtyczkach i motywach, a nie w rdzeniu. Dla właściciela strony najważniejsze jest:

  1. Aktualizuj wtyczki i motywy, najlepiej z automatycznymi aktualizacjami dla sprawdzonych dodatków.
  2. Usuwaj nieużywane i porzucone wtyczki (bez aktualizacji od ponad roku).
  3. Ogranicz liczbę kont administratorów i włącz dla nich 2FA — trwały XSS najczęściej celuje właśnie w nich.
  4. Śledź bazy podatności (np. WPScan, Wordfence, Patchstack) dla używanych wtyczek.

Dla twórców wtyczek i motywów WordPress udostępnia gotowe funkcje do kodowania i oczyszczania danych:

<?php
echo '<h2>' . esc_html( $tytul ) . '</h2>';
echo '<input type="text" value="' . esc_attr( $wartosc ) . '">';
echo '<a href="' . esc_url( $link ) . '">Link</a>';
echo wp_kses_post( $tresc_z_edytora ); // dopuszcza tylko HTML dozwolony we wpisach

// przy zapisie danych z formularza
$nazwa = sanitize_text_field( wp_unslash( $_POST['nazwa'] ?? '' ) );

Zasada w WordPressie: oczyszczaj przy zapisie (sanitize_*), koduj przy wyświetlaniu (esc_*) — zawsze możliwie najpóźniej, tuż przed wypisaniem.

Jak sprawdzić, czy strona jest podatna na XSS

  • Przegląd kodu — szukaj miejsc, gdzie dane od użytkownika trafiają do HTML-a bez kodowania, oraz użycia innerHTML, eval, dangerouslySetInnerHTML, |raw.
  • Narzędzia SAST — analiza statyczna w CI (np. Semgrep, CodeQL) wyłapuje wiele typowych wzorców.
  • Skanery DAST — OWASP ZAP lub Burp Suite przeskanują działającą aplikację na środowisku testowym.
  • Testy penetracyjne — przy aplikacjach przetwarzających dane klientów warto zlecić je regularnie; jak wyglądają, opisuje artykuł testy bezpieczeństwa IT.

Uwaga: Skanuj i testuj wyłącznie własne aplikacje lub te, na których testowanie masz pisemną zgodę właściciela. Testowanie cudzych stron bez zgody jest w Polsce przestępstwem.

XSS a CSRF i inne ataki

XSS bywa mylony z CSRF. Różnica: w XSS atakujący wykonuje własny kod na podatnej stronie, w CSRF tylko nakłania przeglądarkę ofiary do wysłania żądania, korzystając z jej ciasteczek. Co ważne, XSS omija zabezpieczenia przed CSRF — skrypt działający na stronie może odczytać token CSRF i wysłać poprawne żądanie. Szczegóły w tekście o Cross Site Request Forgery. Dlatego eliminacja XSS powinna być priorytetem przy każdym przeglądzie bezpieczeństwa aplikacji.

~ man faq

Najczęściej zadawane pytania

Co to jest atak XSS?

Cross-site scripting to rodzaj ataku, w którym podatna strona umieszcza w swoim kodzie dane od atakującego bez odpowiedniego zabezpieczenia. Przeglądarka ofiary traktuje je jako skrypt strony i wykonuje go z jej uprawnieniami.

Jakie są rodzaje XSS?

Odbity XSS (reflected), w którym złośliwe dane trafiają na stronę z linku lub formularza; trwały XSS (stored), w którym są zapisane w bazie i wyświetlane innym; oraz DOM XSS, w którym błąd leży w kodzie JavaScript działającym w przeglądarce.

Jak zabezpieczyć stronę przed XSS?

Koduj wszystkie dane od użytkowników przy wyświetlaniu zgodnie z kontekstem, korzystaj z automatycznego escapingu frameworka, nie używaj innerHTML dla niezaufanych danych, oczyszczaj dozwolony HTML biblioteką typu DOMPurify i wdroż nagłówek Content-Security-Policy.

Czym różni się XSS od CSRF?

W XSS atakujący uruchamia własny skrypt na podatnej stronie, więc może praktycznie wszystko, co użytkownik. W CSRF nie wykonuje kodu na stronie, tylko nakłania przeglądarkę ofiary do wysłania żądania, np. zmiany ustawień, z jej ciasteczkami.

Czy WAF chroni przed XSS?

Częściowo. Zapora aplikacji webowych blokuje wiele typowych prób ataku i daje czas na załatanie luki, ale da się ją obejść, a DOM XSS często w ogóle nie przechodzi przez serwer. WAF jest dodatkiem, nie zamiennikiem poprawnego kodu.

Ten artykuł jest częścią tematu

CZ

$ whoami

Czarek Zawolski

Założyciel i redaktor XAD.pl. Pisze o sieciach, bezpieczeństwie IT, administracji systemami Windows i Linux oraz o sprzęcie, który sprawia ludziom problemy na co dzień.

~ ls ../podobne