Zum Inhalt springen
Categoria: Pentest8 Min. Lesezeit

Modernes XSS: DOM, Stored und Reflected mit Beispielen aus dem Testlabor

Por Lucas Andrade ·

Drei XSS-Varianten in der Sandbox seziert mit Payloads, Exploit-Flow und Mitigation per strikter CSP, Trusted Types und DOMPurify-Sanitisierung.

Modernes XSS: DOM, Stored und Reflected mit Beispielen aus dem Testlabor

Der HackerOne-Report 2025 fuehrte XSS als zweithaeufigsten gemeldeten Bug in oeffentlichen Bug Bountys, mit einem Median von 750 USD pro Fund und Spitzen um 20.000 USD bei Enterprise-Zielen. Die Bug-Klasse stirbt nicht, weil Browser sich weiterentwickeln, waehrend Frontend-Pipelines weiter Strings in innerHTML kleben. Das Basilisk-Team baut ein Labor mit drei absichtlich loechrigen Apps auf, faengt jeden Request in Burp Suite ab und zeigt jeden Vektor mit Payload, Kontext und Patch. Bevor du irgendetwas kopierst, stelle sicher, dass dein Lab isoliert ist, denn Skripte ohne schriftliche Beauftragung auf Dritte zu feuern bleibt in Deutschland nach Paragraph 202c StGB strafbar.

Die drei XSS-Familien im Ueberblick

XSS ist die Injektion und Ausfuehrung von Angreifer-JavaScript im Sicherheitskontext einer fremden Origin. Drei Familien decken praktisch alle Faelle ab: Reflected (Payload kommt im Request und wird sofort zurueckgespiegelt), Stored (Payload persistiert im Backend und trifft jeden kuenftigen Besucher) und DOM-based (die Injektion passiert komplett clientseitig, ohne dass der Server den Payload je sieht). Die Auswirkung ist in allen drei Faellen dieselbe: der Angreifer erbt die Rechte des Opfers im Browser, kann Session-Token stehlen, Aktionen im Namen des Nutzers ausfuehren und, bei einem Admin, die gesamte Anwendung uebernehmen.

Reflected XSS

Reflected XSS taucht auf, wenn Benutzereingabe ohne saubere Kodierung in der HTTP-Antwort landet, typisch ueber Querystring oder GET-Formular. In unserem Lab klebt eine Suche auf /search?q= den Begriff in ein h2, weshalb das klassische <svg/onload=alert(document.domain)> im Top-Level-Kontext zuendet. Der Unterschied zwischen Amateurbericht und Profi-Report ist der Impact-Nachweis: das Session-Cookie via fetch auf eine Angreifer-Domain zu klauen funktioniert nur ohne HttpOnly-Flag. Halte die Luecke in Burps Historie fest, samt exaktem Request und Antwortkontext, denn ein Report ohne reproduzierbaren Request landet im Duplikat- oder Informativ-Stapel.

Stored XSS

Stored XSS ist die gefaehrlichste Spielart, weil sie in der Datenbank festsitzt und jeden kuenftigen Besucher trifft. In DVWA stellen wir Stufe medium ein, schicken im Kommentarfeld den Payload <img src=x onerror=...>, der das Cookie base64-kodiert an ein Webhook sendet, und sehen das Webhook in Sekunden Moderator-Sessions sammeln. Die reale Angriffsflaeche umfasst Markdown-Renderer, transaktionale E-Mail-Templates, CSV-Exporte, die in Excel oeffnen, sogar EXIF-Metadaten in internen Dashboards. Robuste Verteidigung kombiniert Input-Sanitisierung mit Output-Escaping; eines ohne das andere ist Theater, weil ein sauber gespeicherter Wert im falschen Kontext trotzdem zuendet.

DOM-based XSS

DOM-based XSS passiert komplett im Browser, der Payload erreicht nie das Server-Log. Der Klassiker location.hash in document.write steckt weiter in Legacy-Chat-Widgets und in React-Apps, die Hash-Daten an dangerouslySetInnerHTML weiterreichen. Wir adaptieren Level 1 des Google XSS Game im Lab und demonstrieren Sink-Hunting durch DevTools, aktiviertes 'Pause on exceptions' und die DOM-Invader-Erweiterung aus Burp. Der Triage-Flow ist immer gleich: Source identifizieren (hash, search, postMessage), bis zum Sink verfolgen (innerHTML, eval, setAttribute), mit minimalem Payload validieren, dann eskalieren. Weil der Server nichts sieht, greifen serverseitige WAFs hier nicht.

Kontext ist alles: das richtige Escaping

Derselbe Payload zuendet oder scheitert je nach Injektionskontext. HTML-Body verlangt HTML-Entity-Encoding, ein Attributwert zusaetzlich Quote-Handling, ein <script>-Block JavaScript-String-Escaping, eine URL kontextsensitives URL-Encoding und ein Style-Kontext eigene Regeln. Die Wurzel fast jeder XSS ist ein Wert, der aus einem Kontext in einen anderen wandert, ohne neu kodiert zu werden. Deshalb ist die OWASP-Faustregel: kodieren an der Ausgabe, passend zum Kontext, nie generisch am Eingang filtern. Ein Blacklist-Filter, der nur spitze Klammern entfernt, faellt sofort gegen attributbasierte Vektoren wie onmouseover.

Das Labor aufbauen

Stell heute das Labor mit DVWA, OWASP Juice Shop und einer abgespeckten Next.js-App auf, alles in Docker-Containern in einem isolierten Netz ohne Route ins Internet, ausser einem kontrollierten Webhook fuer den Exfil-Nachweis. Route jeden Browser-Traffic durch Burp, aktiviere das Proxy-History-Logging und lege pro Vektor eine Repeater-Sammlung an. Reproduziere alle drei Vektoren, bis je ein Popup feuert, und notiere fuer jeden den exakten Request, den Injektionskontext und den Antwort-Snippet. Dieses Runbook ist spaeter die Grundlage sauberer Bug-Bounty-Reports mit Ein-Satz-Reproduktion.

Moderne Mitigation: CSP, Trusted Types, DOMPurify

Moderne Mitigation hat aufgehoert, ueber Filter fuer spitze Klammern zu reden. Content Security Policy Level 3 mit Nonce pro Request blockiert Inline-Injection sogar, wenn der Angreifer HTML auf die Seite zwingt, sofern du nicht in die Falle gehst und unsafe-inline als Fallback ergaenzt. Trusted Types verwandelt innerHTML-Zuweisungen in TypeErrors, ausser sie laufen durch eine registrierte Policy. Kombiniert mit DOMPurify fuer reiches HTML misst Google rund 90% weniger Angriffsflaeche. HttpOnly auf dem Session-Cookie neutralisiert den klassischen Cookie-Diebstahl, und SameSite daempft die Weiterreichung an fremde Origins.

Shift-Left: XSS in der CI fangen

Verdrahte die Detektion in die CI, indem du Semgrep mit der Regel javascript.lang.security.audit.xss auf jedem PR laufen laesst, ergaenzt um ESLint-Plugins, die dangerouslySetInnerHTML und rohe innerHTML-Zuweisungen markieren. Ein DAST-Durchlauf gegen die Staging-Instanz faengt die Faelle, die statische Analyse verpasst, etwa reflektierte Parameter in dritten Templates. Wichtig ist, dass ein neuer Sink den Build bricht, nicht nur einen Report erzeugt, den niemand liest. So wird die XSS-Praevention Teil der Definition of Done statt eines nachgelagerten Pentests.

Haeufige Fallstricke

Der erste Fallstrick ist das Blacklisting: jeder Filter, der nur bekannte Payloads blockt, faellt gegen Encoding-Varianten und alternative Event-Handler. Der zweite ist der WAF als einzige Verteidigung, umgangen durch Case-Mixing, Kommentare und Double-Encoding. Der dritte ist die CSP mit unsafe-inline, die faktisch nichts blockt. Der vierte ist die Annahme, ein Framework wie React sei automatisch sicher, waehrend dangerouslySetInnerHTML und href-Injection weiter offen stehen. Der fuenfte ist fehlendes HttpOnly, das jede reflektierte Luecke sofort zum Session-Diebstahl aufwertet.

Checkliste

Pruefe jeden Injektionspunkt in allen fuenf Kontexten (HTML-Body, Attribut, Script, URL, Style). Bestaetige, ob das Session-Cookie HttpOnly und SameSite gesetzt hat. Verifiziere die CSP: keine unsafe-inline, Nonce pro Request, restriktive script-src. Teste, ob Trusted Types erzwungen wird. Belege den Impact mit einem harmlosen, aber eindeutigen Nachweis (Popup mit document.domain oder ein Callback ohne echte Daten). Dokumentiere Ein-Satz-Reproduktion, Impact und Patch-Vorschlag pro Fund, und ordne jeden Befund vor dem Ticket den STRIDE-Buckets Tampering und Elevation zu.

Fortgeschrittene Vektoren jenseits des Popups

Ein alert(1) beweist die Injektion, aber ein reifer Report zeigt die Kette dahinter. Mit einer stabilen XSS lassen sich CSRF-Token aus dem DOM lesen und Anfragen im Namen des Opfers stellen, wodurch die typische CSRF-Verteidigung faellt, weil der Request aus dem legitimen Origin kommt. Ueber die Fetch-API kann der Payload authentifizierte Endpunkte abfragen und Antworten exfiltrieren, einschliesslich Profildaten oder Admin-Panels. Ein Service Worker, per XSS registriert, ueberlebt sogar den Seiten-Reload und faengt kuenftigen Traffic ab. Besonders unterschaetzt ist die Kombination aus XSS und einem offenen postMessage-Handler, der Daten ueber Frame-Grenzen hinweg annimmt: ein einziger fehlender Origin-Check verwandelt eine harmlose Subdomain in einen Brueckenkopf. Deshalb bewerten wir Impact nie am Popup, sondern an der realistisch erreichbaren Aktion: Konto-Uebernahme, Datenabfluss oder Rechteausweitung auf einen Admin, jeweils mit belegtem, aber nicht schaedlichem Nachweis.

Reporting und verantwortungsvolle Offenlegung

Ein technisch korrekter Fund ohne sauberen Report verpufft. Die Struktur, die bei HackerOne, Bugcrowd und Intigriti triagiert wird, ist immer gleich: eine praezise Zusammenfassung, betroffene URL und Parameter, Ein-Satz-Reproduktion mit exaktem Payload, ein belegter Impact und ein konkreter Patch-Vorschlag. Der Impact-Nachweis bleibt bewusst harmlos: ein document.domain-Popup oder ein Callback ohne echte Nutzerdaten, niemals ein Massenexfil realer Sessions. Screenshots und ein kurzer Videoclip beschleunigen die Triage erheblich. Halte dich strikt an den Scope des Programms; ein Treffer ausserhalb des Scopes ist kein Ruhm, sondern ein Rechtsrisiko. Dokumentiere die CVSS-Vektorstring-Begruendung, damit die Severity nicht verhandelbar wirkt. Ein Report, der dem Verteidiger die Arbeit abnimmt, den Fix zu verstehen und zu deployen, wird schneller bezahlt und baut den Ruf auf, der spaeter zu privaten Einladungen mit hoeheren Bounties fuehrt.

Same-Site-Cookies und die Grenzen von HttpOnly

HttpOnly verhindert den direkten Cookie-Zugriff aus JavaScript, aber es ist keine vollstaendige Abwehr gegen XSS-Impact. Selbst mit gesetztem HttpOnly kann der injizierte Code weiterhin authentifizierte Anfragen im Browserkontext des Opfers absetzen, denn der Browser haengt das Cookie automatisch an; der Angreifer braucht das Token gar nicht zu lesen, er nutzt es implizit. Deshalb ergaenzen wir HttpOnly stets um SameSite=Strict oder Lax, kurze Session-Lebensdauern, serverseitige Bindung an Client-Merkmale und, bei sensiblen Aktionen, eine erneute Authentifizierung. Ebenso wichtig ist die Trennung von Subdomains: eine XSS auf einer Marketing-Subdomain darf nicht in die Session der Hauptanwendung greifen, was eine strikte Domain-Attributierung der Cookies und getrennte Origins verlangt. Verteidigung gegen XSS ist immer geschichtet, nie ein einzelnes Flag, das alles rettet.

FAQ: Macht ein modernes Framework XSS unmoeglich?

Nein. React, Angular und Vue escapen Standard-Bindings automatisch, aber sie lassen bewusste Auswege offen: dangerouslySetInnerHTML, v-html, bypassSecurityTrustHtml sowie href- und src-Attribute mit javascript:-URLs. Diese Auswege sind die haeufigste XSS-Quelle in modernen Codebasen. Das Framework reduziert die Flaeche, ersetzt aber nicht CSP, Trusted Types und kontextbewusstes Escaping.

FAQ: Reicht eine WAF gegen XSS?

Nein. Eine WAF ist eine nuetzliche Ausseneschicht, aber sie ist umgehbar durch Encoding, Case-Mixing und protokollnahe Tricks und sieht DOM-based XSS gar nicht, weil der Payload den Server nie erreicht. Behandle die WAF als Fruehwarnung und Rauschfilter, niemals als Ersatz fuer Ausgabe-Escaping und eine strikte CSP.

Fazit

Praktisches Takeaway: stell das Labor auf, reproduziere alle drei Vektoren bis je ein Popup feuert, schraube dann Nonce-CSP, Trusted Types und DOMPurify dazu und spiele exakt dieselben Payloads erneut ab. Was noch zuendet, verdient eine Policy-Anpassung, bis es bricht. Pflege jede Iteration in einem persoenlichen Runbook, denn gute XSS-Reports verlangen Ein-Satz-Reproduktion, belegten Impact und Patch-Vorschlag. XSS ist kein geloestes Problem, sondern eine Disziplin: kontextbewusst kodieren, defense-in-depth schichten und jede neue Ausgabe als potenziellen Sink behandeln, bis das Gegenteil bewiesen ist.

Related posts

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly