|
| 1 | +--- |
| 2 | + |
| 3 | +layout: col-document |
| 4 | +title: WSTG - Latest |
| 5 | +tags: WSTG |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +{% include breadcrumb.html %} |
| 10 | +# Testing for Prototype Pollution |
| 11 | + |
| 12 | +|ID | |
| 13 | +|------------| |
| 14 | +|WSTG-INPV-22| |
| 15 | + |
| 16 | +## Summary |
| 17 | + |
| 18 | +JavaScript is a prototype-based language. Almost every object inherits from `Object.prototype`, and any property that is not found directly on an object is looked up through the prototype chain. Prototype pollution occurs when an application uses attacker-controlled input to set the *keys* of an object during a recursive merge, clone, or path-based assignment, allowing the attacker to reach `Object.prototype` through a special key such as `__proto__`, `constructor`, or `prototype`. Because that prototype is shared by every object in the runtime, the injected property silently becomes visible to unrelated parts of the application. |
| 19 | + |
| 20 | +> Note: This is not the same as [HTTP Parameter Pollution](04-Testing_for_HTTP_Parameter_Pollution.md); despite the similar name, the two vulnerabilities are unrelated. |
| 21 | +
|
| 22 | +Pollution on its own rarely causes harm directly. Its impact depends on a *gadget*: existing code that later reads a property the attacker managed to plant and then uses it in a sensitive way. The same root cause appears in two contexts: |
| 23 | + |
| 24 | +- **Server-side (Node.js)**: depending on the available gadget, impact ranges from denial of service and bypass of security logic to remote code execution. A well-known example is the Kibana RCE, [CVE-2019-7609](https://nvd.nist.gov/vuln/detail/CVE-2019-7609). |
| 25 | +- **Client-side (browser)**: combined with a suitable gadget it commonly leads to DOM-based [Cross-Site Scripting](01-Testing_for_Reflected_Cross_Site_Scripting.md) and can be used to bypass client-side defenses. |
| 26 | + |
| 27 | +## Test Objectives |
| 28 | + |
| 29 | +- Identify functions and libraries that recursively merge, clone, or assign user-controlled properties. |
| 30 | +- Determine whether user input can reach and modify `Object.prototype`. |
| 31 | +- Identify gadgets that turn prototype pollution into a concrete impact. |
| 32 | + |
| 33 | +## How to Test |
| 34 | + |
| 35 | +The following example illustrates the root cause. A naive recursive merge copies every key of an attacker-controlled object into a target: |
| 36 | + |
| 37 | +```javascript |
| 38 | +function merge(target, source) { |
| 39 | + for (const key in source) { |
| 40 | + if (typeof source[key] === "object" && typeof target[key] === "object") { |
| 41 | + merge(target[key], source[key]); |
| 42 | + } else { |
| 43 | + target[key] = source[key]; |
| 44 | + } |
| 45 | + } |
| 46 | + return target; |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +If the source is parsed from user input such as `{"__proto__": {"polluted": "yes"}}`, the assignment walks into `__proto__` and writes onto `Object.prototype`. Afterwards every object in the runtime inherits the planted property: |
| 51 | + |
| 52 | +```javascript |
| 53 | +merge({}, JSON.parse('{"__proto__": {"polluted": "yes"}}')); |
| 54 | +({}).polluted; // "yes" -> Object.prototype was polluted |
| 55 | +``` |
| 56 | + |
| 57 | +### Black-Box Testing |
| 58 | + |
| 59 | +#### Identify the Sources |
| 60 | + |
| 61 | +Prototype pollution is reachable through any input whose keys end up as object property names. Review the application for: |
| 62 | + |
| 63 | +- URL query string and the URL fragment (hash), using bracket or dotted notation, e.g. `?__proto__[key]=value` or `?__proto__.key=value`. |
| 64 | +- JSON request bodies that are deserialized and then merged or cloned (configuration, profile, or settings endpoints are common candidates). |
| 65 | +- Other structured inputs parsed into nested objects, such as form data or cookies. |
| 66 | + |
| 67 | +#### Testing for Client-Side Prototype Pollution |
| 68 | + |
| 69 | +Submit a probe that attempts to set a uniquely-named property on the prototype through a candidate source. The two encodings below express the same intent: |
| 70 | + |
| 71 | +```text |
| 72 | +https://example.com/#__proto__[testpolluted]=reflected |
| 73 | +https://example.com/#constructor[prototype][testpolluted]=reflected |
| 74 | +``` |
| 75 | + |
| 76 | +Then confirm in the browser developer console whether the property leaked onto the global prototype by reading it from a brand-new empty object: |
| 77 | + |
| 78 | +```javascript |
| 79 | +({}).testpolluted; |
| 80 | +// "reflected" -> Object.prototype is polluted via this source |
| 81 | +// undefined -> not polluted through this source |
| 82 | +``` |
| 83 | + |
| 84 | +If the value is returned, the source is exploitable and the next step is to find a gadget that turns the polluted property into DOM XSS (for example, a property a library reads when building markup or configuring script behavior). Browser tooling that scans for both sources and gadgets, such as DOM Invader (see Tools), significantly speeds up this phase. |
| 85 | + |
| 86 | +#### Testing for Server-Side Prototype Pollution |
| 87 | + |
| 88 | +The tester cannot read the prototype from a console here, so detection is indirect: pollute a property and observe an externally visible change in behavior. Send a JSON body that nests the special key inside an otherwise normal object, targeting an endpoint that merges or clones request data: |
| 89 | + |
| 90 | +```http |
| 91 | +POST /api/update HTTP/1.1 |
| 92 | +Host: example.com |
| 93 | +Content-Type: application/json |
| 94 | +
|
| 95 | +{"name":"test","__proto__":{"json spaces":10}} |
| 96 | +``` |
| 97 | + |
| 98 | +A reliable, non-destructive indicator on Express applications is to pollute the `json spaces` property: if a later JSON response from the application comes back pretty-printed (indented) when it previously was not, the server read the indentation setting from the polluted prototype, confirming the vulnerability. Other behavioral indicators include: |
| 99 | + |
| 100 | +- A property the client never sent appearing in subsequent JSON responses. |
| 101 | +- A change in HTTP status, headers, or content negotiation after polluting a property the framework reads internally. |
| 102 | +- A distinctive error or parsing change for inputs that were previously accepted. |
| 103 | + |
| 104 | +#### Assess the Impact |
| 105 | + |
| 106 | +Because impact is gadget-dependent, analyze each confirmed pollution for realistic consequences. On the client-side this is most often DOM XSS or a bypass of a security control. On the server-side, gadgets have historically escalated to denial of service, authentication or authorization bypass, and remote code execution. Treat any confirmed prototype pollution as potentially high severity until gadget analysis rules out impact. |
| 107 | + |
| 108 | +### Gray-Box Testing |
| 109 | + |
| 110 | +When source code is available, search for the vulnerable patterns directly instead of probing blindly. |
| 111 | + |
| 112 | +Look for recursive merge, clone, extend, or deep-assignment routines, whether hand-written or provided by a utility library. Functions that walk a nested path of keys and assign into an object are the primary sinks. A typical grep would target merge and assignment helpers, deep-clone utilities, and property access by user-controlled path. |
| 113 | + |
| 114 | +For each candidate sink, confirm whether keys are validated before assignment. Safe code rejects or skips `__proto__`, `constructor`, and `prototype`, or uses objects without a prototype: |
| 115 | + |
| 116 | +```javascript |
| 117 | +// Vulnerable: blindly assigns into a nested key path |
| 118 | +target[key] = source[key]; |
| 119 | + |
| 120 | +// Safer: refuse dangerous keys |
| 121 | +if (key === "__proto__" || key === "constructor" || key === "prototype") { |
| 122 | + continue; |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +Also confirm the versions of any dependencies with known prototype pollution advisories, since many popular utilities have been patched over time. Where source code is available it is straightforward to trace user input from the request parser to these sinks and establish reachability, and to identify gadgets in the application or its dependencies. |
| 127 | + |
| 128 | +## Remediation |
| 129 | + |
| 130 | +- Sanitize property keys before assignment, explicitly rejecting `__proto__`, `constructor`, and `prototype`. |
| 131 | +- Use objects without a prototype for map-like data, for example `Object.create(null)`, or use the `Map` data structure instead of plain objects. |
| 132 | +- Freeze the base prototype with `Object.freeze(Object.prototype)` where the application's behavior permits it. |
| 133 | +- Validate incoming structured data against a strict schema (for example, JSON Schema) so that unexpected keys are dropped. |
| 134 | +- Prefer well-maintained merge and clone utilities, and keep all dependencies updated to versions that mitigate prototype pollution. |
| 135 | +- On Node.js, the `--disable-proto=delete` runtime flag removes the `__proto__` accessor as a defense-in-depth measure. |
| 136 | + |
| 137 | +## Tools |
| 138 | + |
| 139 | +- [DOM Invader](https://portswigger.net/burp/documentation/desktop/tools/dom-invader) - automated source and gadget discovery for client-side prototype pollution |
| 140 | +- [Burp Suite](https://portswigger.net/burp) - intercepting and crafting JSON payloads for server-side testing |
| 141 | +- [ppmap](https://github.com/kleiton0x00/ppmap) - scanner for client-side prototype pollution |
| 142 | +- [ppfuzz](https://github.com/dwisiswant0/ppfuzz) - prototype pollution fuzzer |
| 143 | + |
| 144 | +## References |
| 145 | + |
| 146 | +- [PortSwigger: Prototype Pollution](https://portswigger.net/web-security/prototype-pollution) |
| 147 | +- [PortSwigger: Widespread Prototype Pollution Gadgets](https://portswigger.net/research/widespread-prototype-pollution-gadgets) |
| 148 | +- [Olivier Arteau: Prototype Pollution Attacks in NodeJS Applications (NorthSec 2018)](https://github.com/HoLyVieR/prototype-pollution-nsec18) |
| 149 | +- [BlackFan: Client-Side Prototype Pollution](https://github.com/BlackFan/client-side-prototype-pollution) |
| 150 | +- [Michał Bentkowski: Prototype Pollution in Kibana (CVE-2019-7609)](https://slides.com/securitymb/prototype-pollution-in-kibana) |
| 151 | +- [CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')](https://cwe.mitre.org/data/definitions/1321.html) |
0 commit comments