< Powrót do Artykułów
◆ Case study · XSS / Output Encoding

Jedno pole wyszukiwania.
Zaufana domena mówiła głosem atakującego.

Techniczne case study XSS: marker, kontekst renderowania, reflected vs stored, wpływ na biznes, CSP, DOM XSS i praktyczne rekomendacje naprawcze.

TRYB black-box webKLASA XSSOBSZAR search / admin panelWPŁYW trusted UI phishing
XSS
brak encodingu
w jednym kontekście
DOM
ryzyko po stronie
frontendu
CSP
ogranicza skutki,
nie naprawia
HIGH
gdy widzi to
pracownik/admin

XSS nie jest “stare i nudne”, gdy działa w zaufanej domenie

Cross-Site Scripting bywa lekceważony, bo „przecież mamy HttpOnly” albo „to tylko alert”. W realnym pentestcie XSS oceniamy przez kontekst: gdzie wykonuje się skrypt, kto widzi payload, czy można modyfikować UI, podszyć się pod komunikat systemowy, wywołać akcje w aplikacji albo zaatakować pracownika w panelu.

Ten case jest zanonimizowany i bezpieczny. Nie pokazujemy payloadów do kradzieży sesji, omijania filtrów ani ataków na użytkowników. Używamy markerów typu BSTA_XSS_MARKER i neutralnych fragmentów HTML pokazujących brak encodingu.

Najpierw szukamy odbicia kontrolowanego tekstu

baseline search
GET /search?q=BSTA_XSS_MARKER HTTP/2 Host: app.example.invalid HTTP/2 200 OK Content-Type: text/html <div class="results-title"> Wyniki dla: BSTA_XSS_MARKER </div> # Marker wraca w HTML. Teraz sprawdzamy encoding kontekstowy.

Odbicie markera nie jest jeszcze podatnością. Kluczowe pytanie brzmi: czy aplikacja koduje znaki specjalne odpowiednio do kontekstu HTML, atrybutu, JavaScriptu albo URL?

Aplikacja kodowała część miejsc, ale nie wszystkie

context test — sanitized
GET /search?q=%3Cb%3EBSTA_SAFE%3C%2Fb%3E HTTP/2 HTTP/2 200 OK <!-- miejsce 1: poprawnie zakodowane --> <span class="query">&lt;b&gt;BSTA_SAFE&lt;/b&gt;</span> <!-- miejsce 2: niepoprawnie wstawione do template --> <div class="empty-state">Brak wyników dla: <b>BSTA_SAFE</b></div> [!] Ten sam input przechodzi przez dwa różne konteksty renderowania.
Wniosek techniczny

Filtr wejścia nie rozwiązuje XSS. Potrzebne jest kodowanie wyjścia zależne od kontekstu. Ten sam parametr może być bezpieczny w jednym miejscu i podatny w drugim.

Od markera do manipulacji zaufanym UI

W bezpiecznym PoC nie wykonywaliśmy agresywnego JavaScriptu. Pokazaliśmy, że można wstawić kontrolowany element w zaufanej domenie i zmienić komunikat widoczny dla użytkownika. Dla biznesu to ważniejsze niż alert: XSS może stać się phishingiem wewnątrz prawdziwej aplikacji.

safe PoC output
Request: GET /search?q=[SAFE_HTML_MARKER] HTTP/2 Rendered effect: -------------------------------------------------- | testpenetracyjny.example.invalid | | Brak wyników. | | [BSTA SAFE MARKER: controlled HTML rendered] | -------------------------------------------------- No cookies read. No external callbacks. No user data touched. Impact demonstrated: attacker controls part of trusted page.

Jeżeli ta sama ścieżka jest dostępna dla zalogowanych użytkowników albo administratorów, wpływ rośnie: fałszywe komunikaty, linki do akcji, clickjacking-like UI deception, wykonywanie requestów w kontekście użytkownika i pivot do innych podatności.

Najgroźniej, gdy payload zapisuje się w systemie

stored XSS pattern — sanitized
POST /api/v1/items HTTP/2 Authorization: Bearer user_token_REDACTED Content-Type: application/json {"name":"BSTA_STORED_MARKER","description":"[controlled harmless HTML marker]"} GET /admin/review/items HTTP/2 Authorization: Bearer reviewer_token_REDACTED HTTP/2 200 OK ... marker rendered in reviewer/admin panel ... [!] Użytkownik niskiego poziomu wpływa na widok pracownika/moderatora.
Severity: Medium → High

Reflected XSS w publicznej wyszukiwarce może być Medium. Stored XSS widoczny w panelu pracownika, moderatora albo administratora zwykle podnosi wagę do High, bo zmienia odbiorcę i zaufany kontekst.

XSS to nie alert.
To możliwość mówienia do użytkownika głosem Twojej domeny.

Co sprawdzamy poza samym payloadem

CTX

Kontekst renderowania

HTML body, atrybut, URL, inline JS i template mają różne reguły kodowania. Jeden sanitizer nie wystarczy.

MAPOWANIE → Output Encoding
CSP

Content Security Policy

Dobra CSP ogranicza skutki XSS, ale nie zastępuje encodingu. Sprawdzamy nonce, unsafe-inline, allowlisty i report-uri.

MAPOWANIE → Defense in Depth
DOM

DOM XSS

Część XSS nie wraca z serwera. Powstaje w JavaScript przez innerHTML, dangerouslySetInnerHTML, document.write albo niebezpieczne templatingi.

MAPOWANIE → Client-Side Security
ROLE

Odbiorca payloadu

Inny wpływ ma XSS widoczny tylko dla autora, a inny payload, który trafia do moderatora, supportu lub admina.

MAPOWANIE → Impact Analysis

Jak zamknąć XSS w praktyce

1. Encoding outputu w kontekście. HTML escape dla treści, attribute escape dla atrybutów, URL validation dla linków, brak inline JS z danymi użytkownika.

2. Bezpieczne komponenty UI. W React/Next unikaj dangerouslySetInnerHTML, a jeśli musisz renderować HTML, użyj sprawdzonego sanitizera i allowlisty tagów.

3. CSP jako ograniczenie skutków. Nonce dla skryptów, brak unsafe-inline, ograniczone connect-src/img-src, raportowanie naruszeń.

4. Testy regresyjne markerów. Dodaj testy z markerami HTML/JS dla każdego pola, które trafia do UI publicznego lub panelu administracyjnego.

regression markers
BSTA_HTML_MARKER = "<b>BSTA</b>" BSTA_ATTR_MARKER = "" autofocus data-bsta=1" BSTA_URL_MARKER = "javascript:/*BSTA*/void(0)" assert rendered_page does_not_execute marker assert rendered_page contains encoded marker or sanitized safe text assert CSP report is generated for blocked inline execution
◆ BSTA · testpenetracyjny.pl

Chcesz wiedzieć, czy Twoja aplikacja jest odporna na XSS?

Przetestujemy reflected, stored i DOM XSS, output encoding, CSP, panele administracyjne oraz realny wpływ na użytkowników i procesy biznesowe.

Zamów test bezpieczeństwa →
Zakres + wycena w 2 dni robocze · raport techniczny + executive summary · pełna anonimizacja danych