Mark's Cybersecurity Write-Ups

Cyber Apocalypse 2023 - Web - Gunhead

During Pandora’s training, the Gunhead AI combat robot had been tampered with and was now malfunctioning, causing it to become uncontrollable. With the situation escalating rapidly, Pandora used her hacking skills to infiltrate the managing system of Gunhead and urgently needs to take it down.

As usual for web challenges, we are provided with a zip file with source code, and a spawnable docker container.

Gunhead

Looks like we’re controlling a robot - cool.

Gunhead

We can toggle a “needs” box along the left, and a sliding-down-from-the-top-like-quake command terminal.

Gunhead

We’ve got some commands we can run - the main ones being /ping and /storage.

/storage gives us some general storage related info.

> /storage
Filesystem: /dev/sda1
Total Space: 20TB
Used Space: 14TB
Available Space: 6TB
Use Percentage: 70%
Mounted On: /

/ping seems to… actually ping things, so that’s interesting.

PING 8.8.8.8 (8.8.8.8): 56 data bytes
> /ping 8.8.8.8
[+] Starting scan on 8.8.8.8

Lets take a look at the provided source code.

In this container’s Dockerfile, we can see the flag getting copied into the container using the following two entries:

# Copy flag
COPY flag.txt /flag.txt
RUN chmod 755 /flag.txt

chmod 755 would mean that any user on the system can read it, including the www user that the web server (nginx in this case) is running as. If this was actually a sensitive file, 755 is not a good choice here.

The rest of the code seems to be a fairly simple web application written in PHP (simple for a professional software developer I guess).

$router = new Router();
$router->new('GET', '/', 'ReconController@index');
$router->new('POST', '/api/ping', 'ReconController@ping');

There is a an API route for the ping command, though not the storage command (which is probably imaginary for the sake of the challenge - fair enough).

Following the code to the ReconController file…

class ReconController
{
    public function index($router)
    {
        return $router->view('index');
    }

    public function ping($router)
    {
        $jsonBody = json_decode(file_get_contents('php://input'), true);

        if (empty($jsonBody) || !array_key_exists('ip', $jsonBody))
        {
            return $router->jsonify(['message' => 'Insufficient parameters!']);
        }

        $pingResult = new ReconModel($jsonBody['ip']);

        return $router->jsonify(['output' => $pingResult->getOutput()]);
    }
}

We get some input from the user in json format specifying an ip address and pass it directly to a new ReconModel. It’s interesting that there is absolutely no input sanitation or checking or anything here - it’s just passing the input directly from the user to ReconModel… maybe there will be some security in there? (I won’t hold my breath, this is a very easy challenge).

class ReconModel
{
    public function __construct($ip)
    {
        $this->ip = $ip;
    }

    public function getOutput()
    {
        # Do I need to sanitize user input before passing it to shell_exec?
        return shell_exec('ping -c 3 '.$this->ip);
    }
}

No sanity checks, and it takes whatever we type in and inserts it directly onto the end of a ping command, which it passes to shell_exec to run a ping on the underlying system.

In PHP, shell_exec:

Execute command via shell and return the complete output as a string

We can probably just chain a command on the end of whatever we want to ping, and have it execute that as well. In Linux systems you can chain commands together in a number of ways:

Since we can use any command on the system, cat is a good choice - it will simply return the contents of a file in plain text, which is what we want to reveal the flag.

So, on the website, if we run the following command, we should get a flag:

/ping 8.8.8.8 && cat /flag.txt
PING 8.8.8.8 (8.8.8.8): 56 data bytes
> /ping 8.8.8.8 && cat /flag.txt

This doesn’t seem to work - likely either the ping command itself is failing, or the shell_exec PHP function is confused when running ping because ping takes a while to actually finish - we never see the results of any of the pings that it does, just that it’s started doing them.

So, lets try ;

/ping 8.8.8.8 ; cat /flag.txt
PING 8.8.8.8 (8.8.8.8): 56 data bytes
HTB{4lw4y5_54n1t1z3_u53r_1nput!!!}
> /ping 8.8.8.8 ; cat /flag.txt

Looks like this worked, and we can see it’s returned the flag.

HTB{4lw4y5_54n1t1z3_u53r_1nput!!!}

How could they have prevented this?