the_open_door
coffee
ESEN
← Back to index

Prototype Pollution

ATT&CKT1190

Prototype Pollution is a JavaScript-specific (Node.js) vulnerability that abuses prototype-based inheritance. In JavaScript, almost every object inherits from Object.prototype; if the attacker manages to write a property onto that global prototype, the property suddenly appears on every object that did not define it explicitly. The typical consequence is escalation: polluting Object.prototype with admin=true makes any user object, when user.admin is read, inherit that true.

The classic vector is a recursive merge/clone function (common when combining a request body with a config object) that copies keys without filtering __proto__. By sending a JSON with that special key, the property is not written on the object but on its prototype:

POST /api/message HTTP/1.1
Content-Type: application/json

{"email":"a@a.com","message":"hi","__proto__":{"admin":true}}

After the merge, Object.prototype.admin is true, so a user who did not have the admin field now inherits it and becomes an administrator. Besides __proto__, depending on how the object is traversed, constructor.prototype paths also work. The impact ranges from privilege escalation to RCE when the polluted property ends up influencing child_process or server-side templates.

# same vectors via query-string / nested form (qs, express)
?__proto__[admin]=true
?constructor[prototype][admin]=true
a[__proto__][admin]=true

# RCE gadget: pollute options node passes to child_process (execSync/spawn)
{"__proto__":{"shell":"/proc/self/exe","argv0":"console.log(require('child_process').execSync('id')+'')//","NODE_OPTIONS":"--require /proc/self/environ"}}

Requirements. The application must (1) merge or clone attacker-controlled data into an object through an unsafe recursive operation (homemade or old-library merge, extend, clone) and (2) later read a property of an object that does not define it, inheriting it from the polluted prototype. If the merge ignores __proto__/constructor or uses Map/prototype-less objects, the attack does not catch.

How to detect it. In traffic, the signature is the presence of the keys __proto__, constructor or prototype in JSON bodies or nested parameters (a[__proto__][admin]=true). On the application side, an anomalous global behaviour (properties appearing on objects that did not have them) after a request with those keys confirms the pollution.

Defence. Sanitise the dangerous keys (__proto__, constructor, prototype) before any merge, use Object.create(null) for objects that receive external data or Map structures, freeze the prototype (Object.freeze(Object.prototype)), and prefer merge libraries that already block these keys. Validate input against a strict schema. It shares its goal (escalate to admin) with Mass Assignment.