Moderne Frontend-Frameworks sind eines der wirksamsten Anti-XSS-Werkzeuge in der Web-Entwicklung. Auto-Encoding beim Rendering, strikte Template-Trennung, eingebaute Sanitization in manchen Frameworks — die Klasse ist deutlich schwerer geworden. Was bleibt: explizite Roh-HTML-Insertion durch dangerouslySetInnerHTML, v-html, [innerHTML], {@html}. Diese Stellen sind die häufigsten XSS-Funde in modernen Apps. Dieser Artikel zeigt die Schutz-Eigenschaften pro Framework, die typischen Anti-Patterns — und SSR-/Hydration-Spezifika.
Wie Auto-Encoding in Frameworks funktioniert
Klassisches Web-Entwicklung im PHP-/Ruby-/ASP-Stil: jede Interpolation muss explizit encodiert werden. Vergessen = XSS.
Moderne Frameworks drehen das um: Default-Auto-Encoding. Wer eine Variable in einem Template interpoliert, bekommt automatisch HTML-Encoding. Erst wer explizit ausschert (mit dangerouslySetInnerHTML und Pendants), übernimmt die Verantwortung.
Konsequenz: XSS in framework-basierten Apps passiert fast nie aus Versehen, sondern fast immer an Stellen, wo Entwickler bewusst Roh-HTML rendern wollten — und dabei Sanitization vergessen haben.
Das macht moderne Pentests effizienter: man sucht nicht alle Interpolationen ab, sondern grep't gezielt nach Roh-HTML-Insertions-APIs.
React
Auto-Encoding:
JSX encodiert alle Variablen-Interpolationen per Default:
function Comment({ text }) {
return <div>{text}</div>;
// <script>alert(1)</script> wird zu <script>alert(1)</script>
}Bewusste Lücke: dangerouslySetInnerHTML.
function Article({ htmlContent }) {
return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />;
// htmlContent wird als Roh-HTML gerendert — XSS wenn nicht sanitisiert
}Der Name ist eine bewusste Warnung: React-Entwickler haben das Property „gefährlich" genannt, um Aufmerksamkeit zu erzwingen. Standard-Lösung: Eingaben vor dem Setzen sanitisieren.
import DOMPurify from 'dompurify';
function Article({ htmlContent }) {
const clean = DOMPurify.sanitize(htmlContent);
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}Weitere React-XSS-Vektoren:
href-Attribute mitjavascript:-URLs — React rendert sie standardmäßig, gibt aber in DevTools Warnung. Schutz: URL-Schema validieren.<script>-Tags viadangerouslySetInnerHTML— funktioniert nicht direkt (React rendert<script>-Tags nicht aus innerHTML aus), aber<img onerror>-Patterns laufen.createElementmit dynamischem Tag-Namen —React.createElement(userTag, ...)mit<script>als Tag → XSS möglich.- Server-Side-Rendering mit String-Konkatenation — wenn SSR-Code Strings manuell zusammenbaut, statt React zu nutzen, ist Auto-Encoding aus.
React Server Components (RSC) und Next.js Hydration:
RSC und SSR brauchen oft JSON-Serialisierung des Initial-State, der in Inline-<script>-Tags eingebettet wird. Dabei muss die Serialisierung gegen </script>-Injection geschützt sein. React und Next.js handhaben das intern; bei eigenem SSR-Code aufpassen.
Vue
Auto-Encoding:
Vue encodiert {{ ... }}-Interpolationen automatisch:
<template>
<div>{{ userInput }}</div>
</template>Bewusste Lücke: v-html.
<template>
<div v-html="userInput"></div>
</template>Vue rendert userInput als Roh-HTML — XSS wenn nicht sanitisiert. Lösung wie bei React: DOMPurify vor dem Binding:
<template>
<div v-html="cleanHtml"></div>
</template>
<script setup>
import { computed } from 'vue';
import DOMPurify from 'dompurify';
const props = defineProps(['userInput']);
const cleanHtml = computed(() => DOMPurify.sanitize(props.userInput));
</script>Weitere Vue-XSS-Vektoren:
v-bind:hrefmit User-URLs ohne Schema-Validation.- Component-Templates aus String-Quellen (
new Vue({ template: userInput })) — sehr selten, aber wenn vorhanden, äquivalent zu Template-Injection. - Render-Functions mit User-Input — wenn man manuell
h()aufruft mit user-kontrolliertem Tag, gleicher Vektor wie ReactcreateElement.
Vue hat keine eingebaute Sanitization — v-html ist nackt Roh-HTML. Sanitization-Library ist Pflicht.
Angular
Auto-Encoding:
Angular encodiert Property-Bindings automatisch:
<div>{{ userInput }}</div>
<div [textContent]="userInput"></div>Eingebaute Sanitization:
Im Gegensatz zu React und Vue hat Angular eingebaute HTML-Sanitization über den DomSanitizer-Service. Wenn man [innerHTML] setzt, läuft Angulars Sanitization automatisch:
<!-- Auto-sanitisiert -->
<div [innerHTML]="userInput"></div>Angular entfernt <script>-Tags, onerror-Attribute, javascript:-URLs aus dem HTML — analog zu DOMPurify, aber Framework-eingebaut.
Bewusste Lücke: bypassSecurityTrustHtml.
Wer Angulars Sanitization umgehen will, muss das explizit machen:
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@Component({...})
export class MyComponent {
constructor(private sanitizer: DomSanitizer) {}
// Schadhaft, wenn userInput von User kommt
getTrustedHtml(userInput: string): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(userInput);
}
}Der Methode-Name ist (wie bei React) eine bewusste Warnung. Wer das Verfahren benutzt, sollte selbst sanitisieren (mit DOMPurify) bevor man Angular's Schutz umgeht.
Trusted Types in Angular:
Angular hat seit Version 11 Trusted-Types-Unterstützung. [innerHTML]-Bindings produzieren automatisch TrustedHTML, kompatibel mit strikten CSP-Settings. Vertieft in trusted-types.
Angular ist im Framework-Vergleich am besten gegen XSS gehärtet — sowohl Auto-Encoding als auch eingebaute Sanitization als auch TT-Integration.
Svelte
Auto-Encoding:
Svelte encodiert { ... }-Interpolationen automatisch:
<div>{userInput}</div>Bewusste Lücke: {@html}.
<div>{@html userInput}</div>Svelte rendert userInput als Roh-HTML — XSS wenn nicht sanitisiert. Wie bei Vue: keine eingebaute Sanitization, DOMPurify als Lösung.
<script>
import DOMPurify from 'dompurify';
export let userInput;
$: cleanHtml = DOMPurify.sanitize(userInput);
</script>
<div>{@html cleanHtml}</div>Svelte 5 Runes-Update:
Svelte 5 (2024 stabil) hat dieselbe {@html}-Direktive mit denselben Risiken. Reactivity wechselt zu Runes, aber XSS-Verhalten ist unverändert.
Vergleichs-Übersicht
| Framework | Auto-Encoding | Eingebaute Sanitization | Roh-HTML-Direktive | TT-Unterstützung |
|---|---|---|---|---|
| React | Ja | Nein | dangerouslySetInnerHTML | Über externe Policy |
| Vue | Ja | Nein | v-html | Über externe Policy |
| Angular | Ja | Ja (DomSanitizer) | [innerHTML] (auto-sanitized) | Nativ (seit v11) |
| Svelte | Ja | Nein | {@html} | Über externe Policy |
| Solid | Ja | Nein | innerHTML-Attribut | Über externe Policy |
| Lit | Ja | Nein | unsafeHTML-Directive | Nativ |
Auswahl-Logik aus XSS-Sicht:
- Angular für Apps mit Compliance-Anforderung (Banking, Healthcare, Behörden) — bestes Out-of-Box-Verhalten.
- React / Vue / Svelte für die meisten Apps — gleich sicher in Auto-Encoding, brauchen Sanitization-Library bei Roh-HTML.
- Lit für Web-Components mit hohem Sicherheits-Anspruch — eingebaute TT-Unterstützung.
In der Praxis ist die Framework-Wahl selten primär durch XSS bestimmt — alle modernen Frameworks sind „sicher genug", solange man die Lücken-Direktiven kennt.
Häufige Anti-Patterns in Framework-Apps
Anti-Pattern 1 — Markdown-Renderer ohne Sanitization.
Apps zeigen Markdown-Content (Blog-Posts, Kommentare, README) und nutzen marked, markdown-it, remark. Default-Konfigurationen erlauben oft Roh-HTML im Markdown — und dessen Output wird per dangerouslySetInnerHTML gerendert. XSS-Tor.
Schutz:
import { marked } from 'marked';
import DOMPurify from 'dompurify';
function renderMarkdown(input) {
const html = marked.parse(input);
return DOMPurify.sanitize(html);
}Oder Markdown-Library nutzen, die Roh-HTML grundsätzlich blockiert (marked mit breaks: true, gfm: true, headerIds: false, mangle: false und kein <html> im Output).
Anti-Pattern 2 — bypassSecurityTrustHtml für „Konfig-HTML".
Angular-Apps mit CMS-Backend, das HTML-Snippets liefert. Entwickler nutzt bypassSecurityTrustHtml aus dem (falschen) Vertrauen ins Backend. CMS-Backend wird kompromittiert oder hat selbst XSS → Schadcode landet in jeder App-Seite.
Schutz: Auch CMS-Output sanitisieren. Backend-Vertrauen ist niemals total.
Anti-Pattern 3 — User-URLs in Links.
function ProfileLink({ user }) {
return <a href={user.website}>Website</a>;
// Wenn user.website "javascript:alert(1)" ist — XSS beim Klick
}Schutz:
function ProfileLink({ user }) {
const safeUrl = isAllowedUrl(user.website) ? user.website : '#';
return <a href={safeUrl}>Website</a>;
}
function isAllowedUrl(url) {
try {
const u = new URL(url, location.origin);
return ['http:', 'https:', 'mailto:'].includes(u.protocol);
} catch {
return false;
}
}Anti-Pattern 4 — eval für „dynamische Logik".
Manche Apps nutzen eval oder new Function(...) für Custom-Formeln, Plugin-Code, A/B-Tests. Jede User-beeinflusste Quelle in eval ist ein RCE-Vektor im Browser.
Schutz: Logik strukturiert serialisieren (JSON-DSL, MathJS, andere Mini-Parser), nicht JS direkt ausführen.
Anti-Pattern 5 — SSR-String-Konkatenation.
// Schadhaft: manuelle String-Konkatenation umgeht Framework-Encoding
const html = `
<html>
<body>
<h1>Hallo, ${user.name}!</h1>
${renderToString(<App user={user} />)}
</body>
</html>
`;user.name ist nicht encoded — wenn es vom User kommt, klassische Reflected XSS. Statt manueller String-Konkatenation Framework-Renderer für die ganze Seite nutzen.
SSR und Hydration: spezielle Risiken
Server-Side-Rendering (Next.js, Nuxt, SvelteKit, Remix, Astro) bringt eigene XSS-Risiken:
Initial-State-Hydration.
SSR rendert HTML auf dem Server und serialisiert den App-State als JSON in einem Inline-<script>-Tag, damit Client-JavaScript ihn beim Hydration aufnimmt:
<script id="__INITIAL_STATE__" type="application/json">
{ "user": { "name": "Alice" }, ... }
</script>Wenn die JSON-Serialisierung nicht </script> escapen würde, könnte ein User-String mit </script><script>alert(1)</script> die Inline-Sequenz vorzeitig schließen. Moderne SSR-Framework-Implementierungen handhaben das korrekt — bei Custom-SSR-Code muss explizit gehandhabt werden.
Frameworks haben sichere Helpers:
- React:
JSON.stringify-Output muss zusätzlich</script>zu<\/script>ersetzen. Bibliotheken wieserialize-javascriptmachen das. - Next.js: behandelt das intern,
next-script-Komponente ist sicher. - Nuxt/Vue SSR: sicher per Default.
Hydration-Mismatch und XSS:
Wenn Server- und Client-Render nicht übereinstimmen (Hydration Mismatch), kann Framework versuchen, den DOM zu „reparieren" — in seltenen Fällen mit unerwarteten Effekten. Praktisch kein direkter XSS-Vektor, aber Quelle subtiler Bugs.
Streaming SSR:
Moderne Streaming-SSR-Implementierungen (React Server Components, Astro Islands) liefern HTML chunk-weise. Encoding-Disziplin bleibt unverändert nötig — Frameworks handhaben das intern.
FAQ
Brauchen moderne Frameworks noch CSP?
Ja, absolut. Framework-Auto-Encoding ist eine Schicht; CSP ist eine andere. Wenn ein Framework-Bug oder ein dangerouslySetInnerHTML-Bypass auftritt, ist CSP die letzte Hürde. Defense-in-Depth heißt: alle Schichten. Vertieft in Content Security Policy.
Was ist mit alten AngularJS-1.x-Apps?
AngularJS 1.x hatte eine andere XSS-Klasse — Sandbox-Bypass in Template-Expressions. Mehrere CVEs über die Jahre. AngularJS ist seit 2022 End-of-Life — Migration zu Angular 2+ ist die einzige nachhaltige Lösung. Wer noch AngularJS produktiv hat, sollte priorisieren.
Hilft TypeScript gegen XSS?
Indirekt. TypeScript-Compiler kennt XSS nicht direkt. Aber: TS macht Roh-HTML-Insertions oft sichtbar (Type-Mismatch-Warnings bei React JSX, SafeHtml-Type in Angular). ESLint-Plugins (z. B. eslint-plugin-react mit no-danger-Regel) finden viele Stellen automatisch.
React's javascript:-Schutz
React warnt seit Version 16 in der Konsole, wenn ein href="javascript:..."-Attribut gerendert wird. Seit React 18 blockt React diese URLs in manchen Konstellationen. Trotzdem: explizite URL-Validierung im Code ist die robuste Lösung.
Sicherheits-Defaults pro Framework
Angular ist out-of-the-box das sicherste Framework — eingebaute Sanitization, TT-Support, strikte Compiler-Defaults. React, Vue und Svelte verlangen, dass Entwickler:innen Sanitization-Libraries und CSP konfigurieren. Beide Wege funktionieren, Angulars Defaults sind aber „forgiving" gegenüber unerfahrenen Teams.
Server-Side Frameworks: Next.js, Nuxt, Remix
Diese Frameworks erben die XSS-Charakteristika ihres jeweiligen Frontend-Frameworks (React, Vue, React). Plus SSR-spezifische Themen: Initial-State-Serialisierung, Streaming, Hydration. Die offiziellen Docs der Framework-Anbieter haben Sicherheits-Sektionen, die unbedingt zu lesen sind.
DOMPurify als universelle Bridge
Egal welches Framework: wer Roh-HTML rendern muss, kommt an DOMPurify nicht vorbei. Die Library hat Adapter für React (RETURN_TRUSTED_TYPE), Vue (Computed Properties), Angular (Pipes), Svelte (Reactive Statements) — alle Frameworks lassen sich damit absichern.
Weiterführende Ressourcen
Externe Quellen
- React — JSX und Encoding
- Vue.js — Security Guide
- Angular — Security Best Practices
- Svelte — Special Tags (@html)
- Next.js — CSP Guide
- DOMPurify
- serialize-javascript — sichere JSON-in-Script
- eslint-plugin-react — no-danger Rule