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:

JSX react-safe.jsx
function Comment({ text }) {
  return <div>{text}</div>;
  // <script>alert(1)</script> wird zu &lt;script&gt;alert(1)&lt;/script&gt;
}

Bewusste Lücke: dangerouslySetInnerHTML.

JSX react-dangerous.jsx
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.

JSX react-safe-html.jsx
import DOMPurify from 'dompurify';

function Article({ htmlContent }) {
  const clean = DOMPurify.sanitize(htmlContent);
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

Weitere React-XSS-Vektoren:

  • href-Attribute mit javascript:-URLs — React rendert sie standardmäßig, gibt aber in DevTools Warnung. Schutz: URL-Schema validieren.
  • <script>-Tags via dangerouslySetInnerHTML — funktioniert nicht direkt (React rendert <script>-Tags nicht aus innerHTML aus), aber <img onerror>-Patterns laufen.
  • createElement mit dynamischem Tag-NamenReact.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:

Vue vue-safe.vue
<template>
  <div>{{ userInput }}</div>
</template>

Bewusste Lücke: v-html.

Vue vue-vhtml.vue
<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:

Vue vue-sanitized.vue
<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:href mit 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 React createElement.

Vue hat keine eingebaute Sanitizationv-html ist nackt Roh-HTML. Sanitization-Library ist Pflicht.

Angular

Auto-Encoding:

Angular encodiert Property-Bindings automatisch:

HTML angular-safe.html
<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:

HTML angular-innerHTML.html
<!-- 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:

TypeScript angular-bypass.ts
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:

Svelte svelte-safe.svelte
<div>{userInput}</div>

Bewusste Lücke: {@html}.

Svelte svelte-html.svelte
<div>{@html userInput}</div>

Svelte rendert userInput als Roh-HTML — XSS wenn nicht sanitisiert. Wie bei Vue: keine eingebaute Sanitization, DOMPurify als Lösung.

Svelte svelte-sanitized.svelte
<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

FrameworkAuto-EncodingEingebaute SanitizationRoh-HTML-DirektiveTT-Unterstützung
ReactJaNeindangerouslySetInnerHTMLÜber externe Policy
VueJaNeinv-htmlÜber externe Policy
AngularJaJa (DomSanitizer)[innerHTML] (auto-sanitized)Nativ (seit v11)
SvelteJaNein{@html}Über externe Policy
SolidJaNeininnerHTML-AttributÜber externe Policy
LitJaNeinunsafeHTML-DirectiveNativ

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:

JavaScript safe-markdown.js
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.

JSX link-anti-pattern.jsx
function ProfileLink({ user }) {
  return <a href={user.website}>Website</a>;
  // Wenn user.website "javascript:alert(1)" ist — XSS beim Klick
}

Schutz:

JSX link-safe.jsx
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.

JavaScript ssr-string-bad.js
// 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:

HTML ssr-initial-state.html
<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 wie serialize-javascript machen 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

/ Weiter

Zurück zu XSS & Content Injection

Zur Übersicht