vuln-class-prototype-pollution
Prototype Pollution, verständlich erklärt
Eine JavaScript-spezifische Bug-Klasse, bei der Eingaben des Angreifers Eigenschaften am globalen Object-Prototype überschreiben und damit jedes Objekt der Anwendung infizieren. Vom kaputten Merge zum Auth-Bypass und zur Remote Code Execution.
Worum es geht
Jedes Objekt in JavaScript erbt Eigenschaften von einem versteckten
Eltern-Objekt, dem Prototype. Einfache Objekte ({}) erben von
Object.prototype, der Wurzel der Kette. Prototype Pollution
entsteht, wenn ein Angreifer eine Eigenschaft auf diese Wurzel
schreiben kann. Jedes Objekt, das die Anwendung jetzt oder in
Zukunft erzeugt, erbt damit die vom Angreifer gesetzte Eigenschaft.
Das Setup ist fast immer dasselbe. Die Anwendung nimmt strukturierte Eingaben (JSON-Body, Querystring, Formulardaten) und mergt sie über einen rekursiven Helfer in ein bestehendes Objekt. Die Merge-Funktion filtert keine speziellen Schlüssel, also setzt eine Payload wie
{ "__proto__": { "isAdmin": true } }
isAdmin: true direkt auf Object.prototype. Zwei Sekunden später
prüft ein völlig unabhängiger Codepfad an anderer Stelle “ist diese
Person Admin?” und bekommt true zurück, weil die Eigenschaft jetzt
auf jedem Objekt sichtbar ist.
Wo das herkommt
Das Muster lebt in einer Handvoll weit verbreiteter Node.js- Bibliotheken und Programmierstile:
- Rekursive Deep-Merge-, Extend- und Set-by-Path-Hilfsfunktionen.
lodash.merge,lodash.set,lodash.defaultsDeep,object-path,set-value,mixin-deep,dot-prop. Jedes davon hat eine veröffentlichte CVE: lodash CVE-2018-3721, CVE-2019-10744; minimist CVE-2020-7598; set-value CVE-2019-10747; und seit Jahren weitere. - Großzügige Querystring-Parser.
qs(der Parser, den Express jahrelang als Standard nutzte) interpretiert?__proto__[admin]=1so, dassadminaufObject.prototypegesetzt wird. CVE-2022-24999 ist die Express-Variante, mit der das schließlich gepatcht wurde. - Selbst geschriebene “Optionen mergen”-Helfer. Das Muster ist so
naheliegend, dass es laufend neu erfunden wird: über die Eingabe
iterieren, Schlüssel auf das Zielobjekt kopieren, in
verschachtelte Objekte rekursieren. In neun von zehn Fällen
vergisst die Implementierung,
__proto__undconstructorherauszufiltern. - Clientseitig, über die URL. “DOM-based Prototype Pollution”:
die Seite liest
location.hashoder einen Query-Parameter, parst ihn als JSON und mergt ihn in ein Konfigurationsobjekt. Eine serverseitige Lücke ist nicht einmal nötig.
Der Bug ist nicht spezifisch für lodash. Er ist eine Eigenschaft von JavaScript selbst in Verbindung mit Code, der nicht damit rechnet, dass die Schlüssel vom Angreifer beeinflusst sein könnten.
Warum das wichtig ist
Prototype Pollution setzt zunächst nur eine Eigenschaft auf
Object.prototype. Ob daraus ein vollständiger Vorfall wird, hängt
davon ab, was der übrige Code mit dieser Eigenschaft anfängt. Die
typischen Eskalationspfade:
- Umgehung von Authentifizierung oder Autorisierung. Code, der
if (user.isAdmin)oderif (req.body.role === 'admin')prüft, liest jetzt das pollutedObject.prototype.isAdminvon jedem Objekt, das es nicht überschreibt. Mehrere CVEs in CMS- Frameworks und SaaS-Adminoberflächen folgen genau diesem Muster. - Remote Code Execution. Nodes
child_process.spawnnutzt ein Options-Objekt, dessenshell- undenv-Eigenschaften polluted werden können. Ein pollutedObject.prototype.shell = '/bin/sh -c angriffsbefehl'macht aus dem nächsten harmlosenspawn()-Aufruf an anderer Stelle der Anwendung eine RCE. Derselbe Trick greift bei Template-Engines (Handlebars CVE-2021-23383), bei Body-Parsern und bei Paket-Install-Hooks. - Cross-Site Scripting. Clientseitige Prototype Pollution trifft oft die Konfiguration eines HTML-Sanitisers oder einer Template- Hilfsfunktion. Beim nächsten Request rendert die Seite vom Angreifer kontrolliertes HTML, weil die polluted Eigenschaft die Sanitierung unbemerkt deaktiviert.
- Crash und Verfügbarkeit. Eine polluted Eigenschaft kann
Invarianten brechen, auf die der nachgelagerte Code angewiesen
ist (etwa
Object.prototype.toString = "..."). Die Anwendung wirft dann bei jedem Request Exceptions.
Wie Angreifer das tatsächlich ausnutzen
Die Ausnutzung läuft in drei Phasen:
- Sink finden. Ein Codepfad, der vom Angreifer beeinflussbare Eingaben durchläuft und in ein Objekt schreibt: ein JSON-Merge- Endpoint, ein Einstellungs-Import, ein Profil-Speichern.
- Pollute. Eine Payload mit Schlüsseln
__proto__,constructoroderprototypeschicken, die die Merge-Funktion bis zuObject.prototypehochwandern lassen. - Gadget auslösen. Einen nachgelagerten Codepfad finden, der die soeben polluted Eigenschaft liest und ihrem Wert oder ihrer Wahrheit Glauben schenkt.
Schritte 1 und 3 sind die eigentliche Arbeit; Schritt 2 ist über alle Ziele hinweg dieselbe Payload. Öffentliche Exploit-Datenbanken katalogisieren pro populärem Framework dutzende Gadgets der Form “polluted Eigenschaft X → Eskalation Y”.
Wie das behoben wird
Drei Schichten, kombiniert angewendet:
Die richtige Datenstruktur für untrusted Input wählen.
- Für Dictionary-artige Daten
Mapstatt Plain Objects nutzen.Mapläuft nicht über die Prototype-Kette, daher infiziert eine Pollution einer Map keine andere. - Für Objektliterale, die strukturell sicher sein sollen,
Object.create(null)verwenden. Das Objekt hat keinen Prototype. - Schlüssel
__proto__,constructorundprototypean der Vertrauensgrenze ablehnen. Ein einziger Guard auf der Input- Parsing-Schicht schützt jeden nachgelagerten Merge-Aufruf.
Die eingesetzten Bibliotheken patchen.
lodash4.17.12+,qs6.9.7+,minimist1.2.6+,handlebars4.7.7+,object-path0.11.5+.npm auditoderpnpm auditausführen und Advisories rund um Prototype Pollution abarbeiten. Praktisch jede populäre Utility-Library hat einen Fix veröffentlicht.
Den Prototype einfrieren.
- Verteidigung in der Tiefe: beim Anwendungsstart
Object.freeze(Object.prototype)aufrufen. Das verhindert, dass irgendein Code (auch nicht auditierte Dependencies) auf den Prototype schreibt. Bibliotheken, die den Prototype monkey-patchen, brechen damit. Vor der Aktivierung also gründlich testen.
Für Client-Code gilt dasselbe, plus: untrusted Daten nicht per
JSON.parse oder Deep-Merge in eigene Konfigurationsobjekte
übernehmen. location.hash und Query-Parameter an der Grenze als
nicht vertrauenswürdig behandeln.
Wie blueredix Prototype Pollution meldet
Der Scanner meldet CVEs in JavaScript-Bibliotheken Dritter, deren
Beschreibung Prototype Pollution nennt, und erkennt verwundbare
Versionen bekannter Kandidaten (lodash, minimist, qs, handlebars),
die per <script>-Tag eingebunden oder in Sourcemaps sichtbar sind.
Den aktiven Versuch, __proto__-Payloads gegen Ihre Anwendung zu
schicken, übernehmen wir nicht. Das ist Penetration-Testing-
Territorium.