Warum Vorbeugung so entscheidend ist
Prototype Pollution ist tückisch:
- Sie wirkt global.
- Sie bleibt lange unentdeckt.
- Sie kann schwerwiegende Folgen haben – von Admin-Bypass bis DoS.
Der beste Schutz ist nicht, Pollution nachträglich „aufzuräumen“, sondern sie gar nicht erst entstehen zu lassen. In diesem Beitrag gehen wir Schritt für Schritt durch die wichtigsten Abwehrstrategien.
1. Schlüssel validieren (Blockliste & Positivliste)
Prototype Pollution passiert fast immer, weil Anwendungen ungeprüfte Eingaben direkt in Objekte übernehmen. Die einfachste Abwehr: gefährliche Schlüssel blockieren.
Blockliste
Mindestens diese Keys sollten niemals aus untrusted Input akzeptiert werden:
__proto__
prototype
constructor
Beispiel:
const FORBIDDEN_KEYS = ["__proto__", "prototype", "constructor"];
function isSafeKey(key) {
return !FORBIDDEN_KEYS.includes(key);
}
function safeMerge(target, source) {
for (const key in source) {
if (!isSafeKey(key)) continue;
const value = source[key];
if (value && typeof value === "object" && !Array.isArray(value)) {
target[key] = safeMerge(target[key] || {}, value);
} else {
target[key] = value;
}
}
return target;
}
Positivliste
Noch besser ist es, nur bekannte Keys zuzulassen.
Beispiel:
const ALLOWED_KEYS = ["name", "email", "age"];
function strictMerge(target, source) {
for (const key of ALLOWED_KEYS) {
if (key in source) {
target[key] = source[key];
}
}
return target;
}
2. Sichere Container nutzen
Object.create(null)
Standardobjekte erben von Object.prototype
. Aber wenn du Objekte mit Object.create(null)
erzeugst, haben sie keinen Prototypen. Damit können sie nicht „vergiftet“ werden.
const safeObj = Object.create(null);
safeObj.foo = "bar";
console.log(safeObj.__proto__); // undefined
Solche „reinen Dictionaries“ sind perfekt, wenn du Benutzereingaben speichern musst.
Map
Wenn du Key-Value-Strukturen brauchst, nimm lieber Map
:
const m = new Map();
m.set("__proto__", "harmlos");
console.log(m.get("__proto__")); // "harmlos"
Eine Map
hat keine Prototypen-Magie – Keys sind einfach nur Strings.
3. Idempotente & sichere Merge-Funktionen
Viele Schwachstellen entstehen durch naive Merge-Implementierungen. Statt eigene Helfer zu schreiben, nutze:
- moderne, gepatchte Versionen von Libraries (z. B. Lodash >= 4.17.12)
- oder schreibe sehr einfache Merges, die nur genau das tun, was du brauchst
Beispiel für eine sichere flache Zuweisung:
Object.assign(target, source);
Das kopiert nur eigene Eigenschaften von source
und beachtet keine Prototype-Ketten.
4. Prüfungen robust machen
Own Properties prüfen
Viele Exploits basieren darauf, dass Anwendungen geerbte Eigenschaften akzeptieren. Das lässt sich leicht verhindern:
if (Object.hasOwn(obj, "isAdmin")) {
// sichere Prüfung
}
Oder kompatibler:
if (Object.prototype.hasOwnProperty.call(obj, "isAdmin")) {
// nur eigene Eigenschaften
}
Damit werden Prototyp-Manipulationen ignoriert.
5. Parser härten
JSON
Wenn du JSON.parse()
nutzt, kannst du einen Reviver verwenden, um gefährliche Schlüssel rauszufiltern:
const FORBIDDEN = ["__proto__", "prototype", "constructor"];
const data = JSON.parse(rawInput, (key, value) => {
if (FORBIDDEN.includes(key)) return undefined;
return value;
});
Querystring / YAML
Viele Parser erlauben verschachtelte Strukturen. Prüfe, ob deine Parser-Version Schutz gegen Pollution hat.
Beispiele:
qs
(Node.js) → ab Version 6.0 fix für__proto__
js-yaml
→ prüft inzwischen auf gefährliche Schlüssel
6. Tiefen- und Größenlimits
Manche Angriffe nutzen extrem tiefe oder breite Objekte, um Parser und Merges zu überlasten.
Setze daher Limits:
function tooDeep(obj, depth = 0, maxDepth = 5) {
if (depth > maxDepth) return true;
if (obj && typeof obj === "object") {
return Object.values(obj).some(v => tooDeep(v, depth + 1, maxDepth));
}
return false;
}
Wenn ein Input zu tief ist, → ablehnen.
7. Runtime-Monitoring
Man kann zur Laufzeit prüfen, ob das Object.prototype
verdächtige Keys hat:
const suspicious = Object.keys(Object.prototype).filter(
key => !["constructor", "toString", "valueOf"].includes(key)
);
if (suspicious.length > 0) {
console.error("Prototype Pollution erkannt!", suspicious);
}
Das ersetzt keine saubere Programmierung, kann aber als Alarmanlage dienen.
8. Security-Tests einbauen
Jede App sollte Unit-Tests gegen Pollution haben. Beispiel:
test("Object.prototype darf nicht manipuliert werden", () => {
expect(({}).polluted).toBeUndefined();
});
Führe Tests nach Requests aus, die untrusted Input verarbeiten.
9. Kein „falsches Fixen“
Einige vermeintliche Lösungen sind gefährlich:
Object.freeze(Object.prototype)
: Klingt gut, bricht aber viele Libraries, die legitime Prototyp-Erweiterungen nutzen.- Nur
__proto__
blocken: Angreifer weichen aufconstructor.prototype
aus. - Nur am Ende prüfen: Pollution muss beim Einlesen verhindert werden, nicht erst, wenn es zu spät ist.
10. Organisation & Updates
Prototype Pollution ist auch ein Ökosystem-Problem:
- Viele Schwachstellen entstehen in beliebten NPM-Paketen.
- Halte deine Dependencies aktuell.
- Nutze Tools wie
npm audit
odersnyk
, um bekannte CVEs früh zu erkennen.
Analogie: Impfungen für das System
Prototype Pollution ist wie eine Infektion, die sich durchs ganze System zieht.
- Blocklisten sind wie Masken: Sie verhindern den direkten Eintritt.
Object.create(null)
ist wie eine Impfung: Manche Objekte können gar nicht „vergiftet“ werden.- Monitoring ist wie Fiebermessen: Es erkennt, wenn etwas nicht stimmt.
Die beste Verteidigung: eine Kombination aus allen Maßnahmen.
Was du mitnehmen solltest
- Gefährliche Keys blocken oder gar nicht erlauben.
- Sichere Container nutzen:
Object.create(null)
oderMap
. - Nur eigene Eigenschaften prüfen, niemals blind
obj.key
. - Parser und Merge-Funktionen absichern.
- Tests und Monitoring einbauen, damit Pollution nicht unbemerkt bleibt.
- Dependencies aktuell halten – viele NPM-Pakete hatten Pollution-Bugs.
Schreibe einen Kommentar