
Passwort vergessen
Wie XSS den DOM erreicht, welche Browser-APIs Senken sind und welche Abwehrmaßnahmen in der Produktion gelten - Sanierung, CSP, Cookies und CSRF-Pairing.
Wenn ein String als Markup oder Skript anstatt als inertem Text interpretiert wird, läuft dieser Inhalt als Code im Ursprung deiner Seite ab - mit denselben Berechtigungen wie dein Bundle. Das ist XSS: kein Styling-Fehler, ein verwirrter Stellvertreter zwischen Daten und Anweisungen. Dieser Beitrag ist die technische Grundlage - Angriffsmechanik, Senken, skalierbare Fixes sowie ein ausführbares Playground (Vite + React auf main; live: playgrounds.lucascoliveira.com) und Hinweise auf dieses Next.js-Repo, wo Überschriften und MDX das Standardrisiko reduzieren.
In Skripte, die in deine Seite injiziert werden, laufen mit dem gleichen Ursprung wie dein Bundle ab. In der Praxis bedeutet das oft:
document.cookie-Sichtbarkeit für Cookies, die nicht als HttpOnly markiert sind (Sitzungdiebstahl / Fixierungsketten).fetch() / XMLHttpRequest zu deiner API mit Umgebungsberechtigungen, wenn Cookies gesendet werden und CORS dies zulässt - oder Exfiltration von Token, die in localStorage / sessionStorage gehalten werden, wenn deine App sie dort platziert hat.Die Abwehr ist also nicht "verwende React" - es ist nie das Zuweisen von angreiferkontrollierten Bytes an eine Senke, die HTML parst oder Skript ausführt, es sei denn, ein geprüfter Pipeline hat sie auf einen sicheren Typ reduziert.
Gespeicherte XSS - Unvertrauenswürdige HTML (oder eine Payload, die nach der Vorlagenexpansion zu HTML wird) wird persistiert (DB, Cache, Suchindex). Jeder Benutzer, der diesen Datensatz lädt, trifft auf die Senke. Frontend-Auswirkung: das erste Rendern, das innerHTML = row.body oder gleichwertig ohne Sanierung ausführt, führt die Payload aus.
Reflektierte XSS - Die Payload persistiert nie; sie springt vom Server oder der Routing-Schicht in die Antwort. Klassisch: ?q=<script>...</script> reflektiert in die HTML ohne Kodierung. SPA-Äquivalent: location.search oder Hash, der clientseitig geparsed und ohne Kodierung in den DOM geschrieben wird. Die Fixierung ist dieselbe: behandle URL-abgeleitete Zeichenfolgen als Daten; wenn du sie reflektieren musst, kodiere für den Kontext (siehe unten).
DOM-basierte XSS - Die Serverantwort ist "sauber", aber clientseitiges Skript liest angreiferkontrollierte Eingabe (location, referrer, postMessage, WebSocket-Nachrichten) und übergibt sie an eine Senke. Beispiel: eval("handle" + location.hash.slice(1)) oder element.innerHTML = decodeURIComponent(...). Statische Analyse von Vorlagen reicht nicht aus; du musst jeden Pfad von unvertrauenswürdiger Eingabe zu Senke prüfen.
Dies sind die üblichen Schuldigen in React-/SPA-Codebasen:
| Senke | Risiko |
|---|---|
element.innerHTML, insertAdjacentHTML | Parst HTML; jedes Tag- / Ereignishandler, das du zulässt, kann Skript ausführen. |
dangerouslySetInnerHTML | Dasselbe wie oben - React saniert nicht. |
document.write | Dasselbe. |
eval, new Function, setTimeout(string) | Direkte Skriptausführung. |
javascript:-URLs in href / src | Navigation oder Ressourcenladung, die als Skript-URL ausgeführt wird. |
postMessage-Handler, die eval oder HTML aus event.data setzen | XSS, wenn origin nicht validiert wird oder event.data eine Senke ohne sicheren Vertrag erreicht - nicht nur "falsche Fenster"-Fehler. |
Keine Senke per Default: textContent, createTextNode, Reacts normale Kindtext-Elemente, Attribute, die React als Zeichenfolgen behandelt, wenn du sie nicht umgehst. Markdown-Pipelines werden zu Senken, wenn sie rohes HTML emittieren und du dieses HTML ohne Sanierung dem DOM zuweist.
Demo-Hinweis (wichtig): In HTML5 werden <script>-Knoten, die über innerHTML / dangerouslySetInnerHTML eingefügt werden, nicht ausgeführt - der Parser führt sie nicht aus, wie ein klassischer reflektierter XSS. Um Ausführung zu sehen, wenn HTML injiziert wird, verlassen sich Payloads typischerweise auf Attribut-Handler (z. B. onerror auf img) oder Ähnliches. Das XSS-Playground hält Payloads vorgefertigt (siehe payloadPresets.ts), damit du Fälle testen kannst, die tatsächlich nach der Insertion auslösen.
Wenn die Benutzeroberfläche nur einfachen Text benötigt - Binde Text mit textContent, React-Textkindern oder MDX, das zu Komponenten kompiliert wird ohne eine HTML-Pipeline. Kein Sanitizer erforderlich; du bist nicht im HTML-Spiel.
Wenn du reichhaltigen Text benötigst (fett, Listen, Links) - Du benötigst entweder eine eingeschränkte Markup-Sprache, die zu sicheren Elementen kompiliert wird oder HTML-Sanierung mit einer Zulassungsliste (Tags + Attribute). Kodierung (z. B. HTML-Entitätskodierung) ist für das Einfügen von Daten in HTML-Textknoten; Sanierung ist für wenn du eine Teilmenge von HTML zulassen musst. Verwechsele die beiden nicht.
Tiefenabwehr für reichhaltigen Text in realen Produkten - Validieren / sanieren beim Schreiben (API lehnt unbekannte Tags, Längenlimits ab) und sanieren oder rendern durch einen sicheren Pfad beim Lesen (Render-Schicht). Speicher kann zurückgesetzt, korrupt oder von einer anderen Serviceversion geschrieben werden.
DOMPurify ist ein Browser-Sanitizer mit einem Standardprofil; du konfigurierst es dennoch für dein Produkt:
ALLOWED_TAGS / ALLOWED_ATTR - Beginne minimal (p, br, strong, em, a mit href nur, wenn du Links benötigst). Jedes zusätzliche Tag ist Angriffsfläche.ADD_ATTR / FORBID_TAGS - Explizit schlägt "fast alles zulassen" vor.RETURN_DOM / RETURN_TRUSTED_TYPE - Bevorzuge DOM-Knoten oder TrustedHTML-ähnliche Ausgabe, wenn du mit Trusted Types integrierst.afterSanitizeAttributes ein - Streiche href-Werte, die mit javascript: beginnen oder seltsame data:-MIME-Typen, wenn du Links zulässt.Im Playground auf main kontrastiert das XSS-Playground einen unsicheren Render-Pfad mit einem sanierten Pfad (DOMPurify-Zulassungsliste) - dieselbe Benutzeroberfläche, unterschiedliche Vertrauensrichtlinie.
CSP reduziert was ausgeführt werden kann, wenn etwas durchkommt. In apps/web/next.config.ts setzt diese Seite eine CSP mit default-src 'self', engem object-src 'none', base-uri 'self', form-action 'self', frame-ancestors 'none', plus script-src / style-src mit 'unsafe-inline', weil Next.js App Router + MUI sx derzeit Inline-Skript-/ Stil- Einbindung in diesem Setup benötigen - dokumentiert im Code. Nonce- oder Hash-basiertes script-src würde die breite Inline-Skriptzulassung entfernen, aber Middleware erfordern, um Noncen pro Anfrage zu injizieren - wert, wenn du selten Benutzer-HTML versendest.
Realitätscheck: CSP ersetzt nicht die Sanierung für Benutzer-HTML; es verengt den Schadensradius (z. B. kann Skript-Hosts blockieren, die du nicht zugelassen hast). Inline-Ereignishandler (onerror usw.) werden nicht automatisch neutralisiert, nur weil du eine CSP setzt - 'unsafe-inline' auf script-src ist in realen Apps üblich (einschließlich dieses Next-/MUI-Setups), und Blockieren von Handlern erfordert normalerweise explizite script-src / script-src-attr (oder Noncen/Hashes), je nach Browser und CSP-Ebene.
XSS kann CSRF-Token umgehen, wenn der Token vom DOM lesbar ist oder wenn das Angreifer-Skript Anfragen mit Berechtigungen ausführt. Also: priorisiere XSS-Fixes; auch:
HttpOnly, Secure, SameSite=Lax oder Strict dort, wo Flüsse es erlauben - reduziert Cross-Site-Kekslecks und klassischen CSRF.SameSite-Cookies, Anti-CSRF-Tokenen oder benutzerdefinierten Headern + CORS-Richtlinie, damit zufällige Seiten keine POST-Anfragen mit Berechtigung ausführen können.Frontend-Aufgabe: füge keine Geheimnisse in JS-lesbaren Speicher ein, wenn vermeidbar; verwende fetch mit expliziter credentials-Richtlinie, die mit deinem API-Design übereinstimmt.
apps/web/next.config.ts: Sicherheitsüberschriften auf /(.*); CSP-String, der im Code mit env-spezifischem script-src (Entwicklung unsafe-eval für React-Stacks nur dort, wo benötigt) erstellt wird.apps/web/app/api/chat/route.ts: JSON-Parsing, leere Überprüfung, MAX_MESSAGE_LENGTH-Kap - Missbrauchsformung, kein XSS an sich.apps/web/lib/blog/mdx.tsx: MDX mit einer festen Komponentenkarte (next-mdx-remote/rsc), keine rohen HTML-Zeichenfolgen aus CMS. Unterschiedliches Bedrohungsmodell als " Nachrichtenrumpf mit HTML".main dir gibtKlon Playgrounds, führe npm install und npm run dev aus → Vite dient der App unter http://localhost:5173.
/insecure mit Payloads aus den Dokumenten hinzu; derselbe Fluss auf /secure zeigt sanierte Ausgabe.payloadPresets.ts und simulateXssImpact.ts für die konkreten Fälle und "Auswirkungs"-Zuordnung.localStorage (siehe **03-session-hijacking)), damit du sehen kannst, was Skript in der Seite lesen kann; insecure-patterns schlägt auch localStorage.getItem('auth_token') in DevTools vor, um es zu überprüfen.Vergleiche /insecure vs. /secure in Elementen und Konsole: dieselben Komponenten, unterschiedliche Behandlung der Zeichenfolge, bevor sie den DOM erreicht.
rg "dangerouslySetInnerHTML|innerHTML|insertAdjacentHTML|eval\\(|new Function" in deiner App und Abhängigkeiten.<img src=x onerror=...>, javascript:-URLs - und denke daran, dass innerHTML nicht <script> ausführt, wie viele Checklisten implizieren. Für innerHTML-Injektionsdemos bevorzuge onerror auf img (oder ähnlich); <svg onload> ist oft unzuverlässig, wenn es auf diese Weise eingefügt wird.SameSite, Berechtigungen und CSRF-Strategie mit Backend aus; gehe davon aus, dass XSS und CSRF gekettet werden.XSS ist Kontrollfluss: Daten, die in Interpretation übergehen. Schutz ist Typisierung an der Grenze: einfacher Text, sichere strukturierte Komponenten oder saniertes HTML mit einer minimalen Zulassungsliste - plus CSP und Keks-Semantik, die begrenzen, was ein streunendes Skript noch tun kann. Das Playground macht diese Grenze sichtbar: ein unsicherer Render-Pfad vs. ein sanierter, plus vorgefertigte Payloads und zugeordnete "Auswirkungs"-Verhaltensweisen im Repo.