Teil 4 – Prototype Pollution finden und nachvollziehen

Warum das Testen so tricky ist

Prototype Pollution ist keine Schwachstelle, die man wie SQL Injection mit einem ' OR 1=1---Payload sofort sieht. Sie ist subtiler:

  • Der Angriff funktioniert über verschachtelte Schlüssel (__proto__, constructor.prototype).
  • Der Effekt tritt oft erst später auf, wenn die Anwendung auf geerbte Eigenschaften zugreift.
  • Scanner übersehen sie häufig, weil sie „nur“ die Antwort einer API prüfen, nicht aber den internen Objektzustand.

Umso wichtiger: zu verstehen, wie man selbst systematisch prüft.


Schritt 1 – Code-Smells im Review

Bevor du überhaupt Requests abfeuerst, lohnt sich ein Blick in den Code (falls du Zugriff hast).

Verdächtige Muster

  • Deep Merge Funktionen
    • merge, deepMerge, extend, defaultsDeep, assignDeep
  • Untrusted Input → Objekt
    • JSON-Body (req.body)
    • Querystrings mit verschachtelten Parametern
    • YAML- oder Config-Parser
  • Kein Key-Filter
    • Nirgends wird geprüft: if (key === "__proto__") continue;

Wenn du sowas siehst, solltest du hellhörig werden.


Schritt 2 – Kleines Test-Labor bauen

Am einfachsten verstehst du Pollution, wenn du sie in einer Mini-App nachstellst.

Beispiel: Unsicherer Merge

function unsafeMerge(target, source) {
  for (const key in source) {
    if (typeof source[key] === "object" && source[key] !== null) {
      if (!target[key]) target[key] = {};
      unsafeMerge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

Jetzt Payload rein:

const payload = { constructor: { prototype: { polluted: "YES" } } };
unsafeMerge({}, payload);

console.log(({}).polluted); // "YES"

Damit siehst du live, wie der Angriff funktioniert.


Schritt 3 – HTTP-Test mit Express

Prototype Pollution taucht oft bei Web-APIs auf. Machen wir ein Mini-Beispiel:

const express = require("express");
const app = express();

app.use(express.json());

function unsafeMerge(target, source) {
  for (const key in source) {
    if (typeof source[key] === "object") {
      target[key] = unsafeMerge(target[key] || {}, source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

app.post("/merge", (req, res) => {
  const defaults = { debug: false };
  const config = unsafeMerge(defaults, req.body);
  res.json({ config, probe: ({}).polluted });
});

app.listen(3000, () => console.log("Server läuft auf Port 3000"));

Angriff:

curl -X POST http://localhost:3000/merge \
  -H "Content-Type: application/json" \
  -d '{"constructor":{"prototype":{"polluted":"YES"}}}'

Antwort:

{
  "config": { "debug": false },
  "probe": "YES"
}

Damit kannst du Pollution in Aktion sehen.


Schritt 4 – Test-Payloads kennen

Es gibt ein paar „klassische“ Payloads, die du systematisch probieren solltest:

1. __proto__

{ "__proto__": { "polluted": "YES" } }

2. constructor.prototype

{ "constructor": { "prototype": { "polluted": "YES" } } }

3. prototype

{ "prototype": { "polluted": "YES" } }

Nach jedem Test prüfen:

console.log(({}).polluted);

Wenn dort „YES“ steht → Bingo: Pollution.


Schritt 5 – Systematisch vorgehen

Prüfen, wie Input verarbeitet wird

  • Werden verschachtelte Keys erlaubt?
  • Gibt es einen Parser, der [a][b][c]-Syntax zu Objekten macht?

Testfälle definieren

  • Für jede API-Route, die JSON/Querystrings akzeptiert.
  • Für jede Funktion, die Eingaben in Objekte merged.

Nachwirkungen prüfen

Prototype Pollution zeigt sich oft nicht sofort. Du kannst nach dem Request prüfen, ob neue Objekte plötzlich zusätzliche Eigenschaften haben.

Beispiel:

app.get("/check", (req, res) => {
  res.json({ probe: ({}).polluted });
});

Nach dem Angriff sollte probeundefined sein.


Schritt 6 – Tools nutzen

Burp Suite + Turbo Intruder

  • Mit Burp kannst du Payloads wie __proto__ oder constructor[prototype] ins JSON oder Query einfügen.
  • Mit Turbo Intruder lassen sich viele Varianten automatisiert testen.

Ffuf/Wfuzz

Du kannst Payload-Listen mit gefährlichen Keys durchtesten.

Semi-automatische Scanner

Einige spezialisierte Tools wie ppfuzzer oder prototype-pollution-finder durchsuchen Libraries nach Schwachstellen.

Aber: Verlass dich nicht nur auf Tools. Pollution braucht oft manuelles Denken.


Schritt 7 – Sauber aufräumen

Prototype Pollution wirkt global. Wenn du sie in einer Testumgebung ausprobierst, denke daran, aufzuräumen:

delete Object.prototype.polluted;
delete Object.prototype.isAdmin;

Sonst können Tests falsche Ergebnisse liefern.


Schritt 8 – Grenzen verstehen

Nicht jede erfolgreiche Pollution führt automatisch zum Exploit.

  • Ja: Wenn Sicherheitsprüfungen oder Flags betroffen sind.
  • Nein: Wenn die App nie auf geerbte Eigenschaften schaut.

Deshalb ist es wichtig, Pollution mit der Geschäftslogik der Anwendung zu verbinden.


Reale Prüf-Strategie (zusammengefasst)

  1. Suche nach Merge-Funktionen und Parsern.
  2. Probiere Payloads mit __proto__, constructor.prototype, prototype.
  3. Prüfe danach, ob ( {} ).polluted gesetzt wurde.
  4. Beobachte, ob Sicherheitslogik, Flags oder Defaults beeinflusst werden.
  5. Räum auf, damit Tests sauber bleiben.

Analogie: Gift-Test im Wasser

Erinnerst du dich an die Stadtwasser-Analogie?

  • Normalerweise schmeckst du nichts, wenn du nur in deinem Haus testest.
  • Wenn du aber das Hauptrohr vergiftest, kommt das Gift überall an.

Prototype Pollution testen heißt: „ein paar Tropfen in die Hauptleitung kippen und gucken, ob alle Häuser betroffen sind“.


Was du bis hier mitnehmen solltest

  • Prototype Pollution testet man, indem man verdächtige Keys wie __proto__ einschleust.
  • Wichtig: Immer danach prüfen, ob neue Objekte plötzlich diese Eigenschaften besitzen.
  • Tools helfen, aber das Verständnis für die Prototype Chain ist entscheidend.
  • Sauberes Aufräumen nach Tests ist Pflicht, sonst gibt’s falsche Ergebnisse.

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert