Prototype Pollution: Die JavaScript-Sicherheitslücke, die Ihre gesamte App vergiftet ☣️

Im Bereich der Webanwendungssicherheit kündigen sich einige Schwachstellen mit dramatischen Abstürzen oder offensichtlichen Sicherheitsverletzungen an. Andere arbeiten still und heimlich, indem sie Ihre Anwendung von Grund auf beschädigen. Prototype pollution gehört zur letzteren Kategorie, eine subtile, aber verheerende Schwachstelle, die JavaScript’s prototypbasierte Vererbung ausnutzt, um jede Objekt in Ihrer Anwendung mit einer einzigen bösartigen Eingabe zu vergiften.
Das Verständnis der JavaScript-Prototype-Kette
Bevor wir in den Angriff eintauchen, müssen wir verstehen, wie JavaScript’s prototypbasierte Vererbung funktioniert. Anders als klassische objektorientierte Sprachen nutzt JavaScript Prototypen, um Eigenschaften und Methoden zwischen Objekten zu teilen. Jedes JavaScript-Objekt hat eine interne Verbindung zu einem anderen Objekt, genannt sein Prototype, wodurch die sogenannte Prototype-Kette entsteht.
Wenn Sie auf eine Eigenschaft eines Objekts zugreifen, prüft JavaScript zuerst, ob diese Eigenschaft direkt auf dem Objekt existiert. Falls nicht, wandert es die Prototype-Kette nach oben, prüft jeden Prototype, bis es entweder die Eigenschaft findet oder das Ende der Kette bei Object.prototype erreicht. Dieser elegante Mechanismus ermöglicht effizientes Teilen von Eigenschaften, schafft aber auch eine gefährliche Angriffsfläche.
Betrachten Sie diesen scheinbar harmlosen Code:
const user = { name: 'Alice' };
console.log(user.toString); // [Function: toString]
Obwohl wir keine toString-Eigenschaft auf dem user-Objekt definiert haben, findet JavaScript sie, indem es die Prototype-Kette bis zu Object.prototype durchläuft, wo toString definiert ist. Dieses Verhalten ist grundlegend für JavaScript, wird aber gefährlich, wenn Angreifer diese Prototypen manipulieren können.
Was ist Prototype Pollution?
Prototype pollution ist eine Schwachstelle, die es Angreifern ermöglicht, Eigenschaften in bestehende JavaScript-Prototypen einzuschleusen, indem sie die Tatsache ausnutzen, dass JavaScript die Modifikation aller Objektattribute erlaubt, einschließlich spezieller Eigenschaften wie __proto__, constructor und prototype.
Der Angriff funktioniert, indem er die Art und Weise ausnutzt, wie JavaScript die Zuweisung von Objekt-Eigenschaften handhabt. Wenn eine Anwendung Benutzereingaben in bestehende Objekte ohne ordnungsgemäße Validierung zusammenführt, kann ein Angreifer bösartige Eingaben erstellen, die den Basis-Object.prototype modifizieren. Da fast jedes Objekt in JavaScript von Object.prototype erbt, wirkt sich diese einzelne Änderung auf die gesamte Anwendung aus.
Solche Schwachstellen entstehen typischerweise, wenn JavaScript-Funktionen rekursiv Objekte mit benutzereingebbaren Eigenschaften zusammenführen, ohne die Schlüssel vorher zu bereinigen, insbesondere beim Verarbeiten von JSON-Daten aus unzuverlässigen Quellen.
Die Anatomie eines Prototype Pollution-Angriffs
Schauen wir uns an, wie ein realer Prototype Pollution-Angriff mit einer anfälligen Merge-Funktion funktioniert, ähnlich denen in beliebten Bibliotheken. Hier ein vereinfachtes Beispiel:
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
if (!target[key]) target[key] = {};
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
// Normale Verwendung
const user = {};
merge(user, { name: 'Alice', role: 'user' });
console.log(user); // { name: 'Alice', role: 'user' }
Diese Funktion erscheint harmlos. Sie führt rekursiv Eigenschaften eines Quellobjekts in ein Zielobjekt zusammen. Aber sehen Sie, was passiert, wenn ein Angreifer bösartige Eingaben liefert:
// Bösartige Nutzlast
const maliciousInput = JSON.parse('{"__proto__": {"isAdmin": true}}');
// Prototype vergiften
merge({}, maliciousInput);
// Jetzt hat JEDES Objekt die Eigenschaft isAdmin
const normalUser = {};
console.log(normalUser.isAdmin); // true - Vergiftung erfolgreich!
Der Angreifer nutzte die spezielle __proto__-Eigenschaft, um auf Object.prototype zuzugreifen und eine isAdmin-Eigenschaft einzuschleusen. Jetzt erben alle Objekte in der Anwendung, inklusive neu erstellter Objekte, die nie mit unserer Merge-Funktion in Berührung kamen, diese vergiftete Eigenschaft.
Auswirkungen in der Praxis: Die Lodash-Fallstudie
Die beliebte Lodash-Bibliothek ist von Prototype Pollution-Schwachstellen betroffen, insbesondere in Funktionen wie defaultsDeep, merge und mergeWith, die es böswilligen Nutzern ermöglichen, das Prototype von Object über __proto__ zu modifizieren, was potenziell Millionen von Anwendungen weltweit betrifft.
Betrachten wir diesen anfälligen Code, der Lodashs merge-Funktion nutzt:
const _ = require('lodash');
const express = require('express');
const app = express();
app.use(express.json());
app.post('/api/update-settings', (req, res) => {
const userSettings = {};
// Anfällig: Zusammenführung untrusted user input
_.merge(userSettings, req.body);
// Später in der Anwendung
if (userSettings.isAdmin) {
// Admin-Zugriff gewähren
return res.json({ access: 'admin' });
}
res.json({ access: 'user' });
});
Ein Angreifer könnte diese Nutzlast senden:
{
"__proto__": {
"isAdmin": true
}
}
Diese einzelne Anfrage vergiftet das Prototype, und plötzlich erben alle Objekte in der Anwendung, inklusive jener für Berechtigungsprüfungen, die isAdmin auf true gesetzt haben. Die Auswirkungen breiten sich in der gesamten Anwendung aus.
Von Vergiftung zu Remote Code Execution
Die Folgen von Prototype Pollution gehen weit über einfache Privilegieneskalation hinaus. In Node.js-Umgebungen können geschickte Angreifer Prototype Pollution mit anderen Schwachstellen kombinieren, um Remote Code Execution (RCE) zu erreichen. Dies geschieht durch eine Technik namens “Gadget Chains”, bei der vergiftete Eigenschaften mit bestehenden Codepfaden in unerwarteter Weise interagieren.
Beispielsweise kann eine Anwendung, die Kind-Prozesse startet und auf vererbte Eigenschaften bei der Befehlsbildung angewiesen ist, durch Prototype Pollution bösartige Befehle einschleusen:
// Anfälliger Code
const { spawn } = require('child_process');
function executeCommand(options) {
const defaultOptions = {};
// Optionen könnten vergiftete Eigenschaften erben
const finalOptions = Object.assign(defaultOptions, options);
spawn('node', [finalOptions.script || 'default.js'], {
shell: finalOptions.shell || false,
env: finalOptions.env || process.env
});
}
Wenn ein Angreifer das Prototype mit {"shell": true, "script": "; malicious-command"} vergiftet, könnte er bei unzureichender Überprüfung der Eigenschaften Code ausführen.
Cross-Site Scripting durch Prototype Pollution
Angreifer können Prototypen mit Eigenschaften wie innerHTML, src oder onerror vergiften. Wenn die Anwendung diese Eigenschaften später referenziert und in den DOM einfügt, wird Cross-Site Scripting (XSS) möglich. Diese Variante ist besonders gefährlich in clientseitigen JavaScript-Frameworks:
// Anfälliges Template-Rendering
function renderContent(element, data) {
element.innerHTML = data.content || 'Standardinhalt';
}
// Angreifer vergiftet den Prototype
const malicious = JSON.parse('{"__proto__": {"content": "<img src=x onerror=alert(document.cookie)>"}}');
merge({}, malicious);
// Später im Code
const emptyData = {};
renderContent(document.getElementById('output'), emptyData);
// XSS durch vergifteten Prototype ausgelöst!
Erkennung in der Praxis
Forscher, die fortschrittliche Fuzzing-Techniken verwenden, haben 65 neue Prototype Pollution-Schwachstellen in Zero-Day-Szenarien entdeckt, die mit herkömmlichen Methoden nicht erkannt werden konnten. Dies zeigt, wie verbreitet dieses Problem weiterhin ist. Das Problem bei Prototype Pollution ist seine Subtilität. Im Gegensatz zu SQL-Injection oder XSS, die oft sofortige Fehler oder sichtbare Effekte erzeugen, kann Prototype Pollution still in Ihrem Code verbleiben und auf die richtigen Bedingungen warten, um katastrophale Fehler zu verursachen.
Verteidigungsmaßnahmen: Fehlerresistente Anwendungen bauen
Der Schutz Ihrer Anwendung vor Prototype Pollution erfordert einen mehrschichtigen Ansatz, der sichere Programmierpraktiken, Eingabekontrolle und architektonische Entscheidungen kombiniert.
Verwendung von Object.create(null) für Wörterbücher
Objekte ohne Prototypen, erstellt mit Object.create(null), durchbrechen die Prototype-Kette und verhindern Vergiftungen. Dies ist eine der effektivsten Verteidigungsmaßnahmen:
// Sicher: Kein Prototyp
const userSettings = Object.create(null);
userSettings.name = 'Alice';
// Kann nicht vergiftet werden
const malicious = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.assign(userSettings, malicious);
console.log(userSettings.isAdmin); // undefined - geschützt!
Objekte, die auf diese Weise erstellt wurden, haben keinen Prototyp und sind somit immun gegen Prototype Pollution. Verwenden Sie dieses Muster für alle Objekte, die benutzereingebbare Daten enthalten.
Implementierung von Property-Validierung und -Sanitization
Validieren und säubern Sie immer Objekt-Schlüssel, bevor Sie Benutzereingaben verarbeiten:
function secureMerge(target, source) {
const dangerousKeys = ['__proto__', 'constructor', 'prototype'];
for (let key in source) {
// Gefährliche Schlüssel blockieren
if (dangerousKeys.includes(key)) {
continue;
}
// Zusätzliche Validierung
if (typeof key !== 'string' || key.startsWith('_')) {
continue;
}
if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) target[key] = Object.create(null);
secureMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
Verwendung von Object.freeze() für kritische Objekte
Frieren Sie Prototypen und kritische Objekte ein, um Änderungen zu verhindern:
// Verhindert Prototype Pollution auf der Root-Ebene
Object.freeze(Object.prototype);
Object.freeze(Object);
// Konfigurationsobjekte einfrieren
const config = Object.freeze({
apiUrl: 'https://api.example.com',
timeout: 5000
});
Diese Methode ist effektiv, kann aber legitimen Code, der auf Prototyp-Änderungen angewiesen ist, beeinträchtigen. Verwenden Sie sie selektiv für sicherheitskritische Objekte.
Verwendung von Map anstelle von Plain Objects
Als Best Practice verwenden Sie Map anstelle von Object zum Speichern von Schlüssel-Wert-Paaren, da Maps nicht von Object.prototype erben:
// Sichere Alternative zu Objekten
const userSettings = new Map();
userSettings.set('name', 'Alice');
userSettings.set('role', 'user');
// Nicht anfällig für Prototype Pollution
console.log(userSettings.get('isAdmin')); // undefined
Maps bieten eine sauberere API für Schlüssel-Wert-Speicherung und eliminieren das Risiko der Prototype Pollution vollständig.
Schema-Validierung implementieren
Verwenden Sie Schema-Validierungsbibliotheken, um strikte Objektstrukturen durchzusetzen:
const Joi = require('joi');
const userSchema = Joi.object({
name: Joi.string().required(),
email: Joi.string().email().required(),
role: Joi.string().valid('user', 'admin').required()
}).unknown(false); // Unbekannte Eigenschaften ablehnen
app.post('/api/users', (req, res) => {
const { error, value } = userSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details });
}
// Sicher, um die validierten Daten zu verwenden
processUser(value);
});
Schema-Validierung stellt sicher, dass nur erwartete Eigenschaften an die Anwendungslogik gelangen, und blockiert Prototype Pollution bei der Eingabe.
Abhängigkeiten regelmäßig aktualisieren
Halten Sie Ihre Abhängigkeiten aktuell, insbesondere sicherheitskritische Bibliotheken. Moderne Versionen beliebter Bibliotheken wie Lodash haben Prototype Pollution-Schwachstellen behoben, vorausgesetzt, Sie verwenden aktuelle Versionen. Nutzen Sie Tools wie npm audit oder Snyk, um anfällige Abhängigkeiten zu identifizieren:
npm audit
npm audit fix
Strikte Modi aktivieren
Der JavaScript-Striktemodus bietet zusätzlichen Schutz:
'use strict';
// Strikter Modus verhindert versehentliche globale Variablenerstellung
// und lässt einige stille Fehler zu Ausnahmen werden
function processData(input) {
// Sicherere Ausführungsumgebung
}
Tests auf Prototype Pollution
Fügen Sie Tests auf Prototype Pollution in Ihre Sicherheits-Test-Suite ein:
describe('Prototype Pollution Tests', () => {
it('sollte Object.prototype nicht vergiften', () => {
const original = Object.prototype.toString;
// Versuch der Vergiftung
const malicious = { "__proto__": { "isAdmin": true } };
merge({}, malicious);
// Keine Vergiftung überprüfen
const testObj = {};
expect(testObj.isAdmin).toBeUndefined();
expect(Object.prototype.toString).toBe(original);
});
it('sollte __proto__ in JSON-Eingaben ablehnen', () => {
const input = '{"__proto__": {"polluted": true}}';
const result = safeJSONParse(input);
expect({}.polluted).toBeUndefined();
});
});
Das große Ganze: Sicher von Haus aus
Prototype Pollution ist ein Beispiel für ein grundlegendes Prinzip in der Anwendungssicherheit: Verteidigung in der Tiefe. Kein einzelner Ansatz eliminiert das Risiko vollständig. Stattdessen sollten mehrere Verteidigungsstrategien kombiniert werden:
- Verwendung von prototype-freien Objekten (
Object.create(null)) für Benutzerdaten - Validierung und Sanitisierung aller Eingaben, insbesondere Objekt-Schlüssel
- Einfrieren kritischer Objekte und Prototypen
- Verwendung von Map statt Plain Objects für Wörterbücher
- Implementierung von strikter Schema-Validierung
- Regelmäßige Aktualisierung der Abhängigkeiten
- Durchführung regelmäßiger Sicherheitstests
- Überwachung auf ungewöhnliche Property-Access-Muster in der Produktion
Fazit
Prototype Pollution zeigt, wie eine scheinbar harmlose Sprachfunktion zu einer ernsthaften Sicherheitslücke werden kann, wenn sie mit untrusted input genutzt wird. Durch die Ausnutzung der JavaScript-Prototype-Kette können Angreifer Eigenschaften einschleusen, die jede Objekt in Ihrer Anwendung vergiften, was zu Privilegieneskalation, Denial of Service oder sogar Remote Code Execution führen kann.
Die gute Nachricht ist, dass Prototype Pollution durch diszipliniertes Programmieren verhindert werden kann. Wenn Sie die Angriffsvektoren verstehen, robuste Eingabevalidierung implementieren, sicherere Objekt-Erstellungsmuster verwenden und Ihre Abhängigkeiten aktuell halten, können Sie JavaScript-Anwendungen bauen, die diese subtile, aber verheerende Schwachstelle widerstehen.
Denken Sie daran: In JavaScript ist Ihre Anwendung nur so sicher wie ihre Prototype-Kette. Eine vergiftete Prototype kann Ihre gesamte App vergiften, aber mit den richtigen Verteidigungsstrategien können Sie sicherstellen, dass Ihre Objekte rein bleiben und Ihre Anwendung sicher bleibt.
Related InstaTunnel pages
Continue from this article into the most relevant product guides and workflows.
Related Topics
Keep building with InstaTunnel
Read the docs for implementation details or compare plans before you ship.