5.1.2. Deserialization

5.1.2.1. PHP Serialization Implementation

5.1.2.1.1. Common Processors

There are several types of serialization processing in PHP, namely php, php_serialize, php_binary and WDDX (support needs to be enabled at compile time). The default is php, which can be modified use session.serialize_handler.

If PHP is compiled with WDDX support, only WDDX can be used. WDDX has been deprecated since PHP 7.4. php_serialize is available since PHP 5.5.4. php_serialize simply uses the serialize/unserialize functions directly internally and does not have the limitations that php and php_binary have.

The format of the PHP processor is: key name + vertical bar + value serialized by the serialize() function.

The format of the php_binary processor is: the ASCII character corresponding to the length of the key name + the key name + the value serialized by the serialize() function.

The format of the php_serialize processor is: an array serialized by the serialize() function.

5.1.2.1.2. Serialization Format

The implementation of php_serialize is in php-src/ext/standard/var.c , the main function is php_var_serialize_intern, the serialized format is as follows:

  • boolean
    • b:<value>;

    • b:1; // true

    • b:0; // false

  • integer
    • i:<value>;

  • double
    • d:<value>;

  • NULL
    • N;

  • string
    • s:<length>:"<value>";

    • s:1:"s";

  • array
    • a:<length>:{key, value};

    • a:1:{s:4:"key1";s:6:"value1";} // array("key1" => "value1");

  • object
    • O:<class_name_length>:"<class_name>":<number_of_properties>:{<properties>};

  • reference
    • pointer type

    • R:reference;

    • O:1:"A":2:{s:1:"a";i:1;s:1:"b";R:2;}

    • $a = new A();$a->a=1;$a->b=&$a->a;

5.1.2.1.3. private and protect

Unlike protect variables and public variables, private cannot be set directly.

The private property can only be accessed within the class in which it is defined, and will not be inherited. Adding the class name before the property is %00className%00 used to denote that it is private.

The protected attribute can be accessed in the parent class and the child class, and the variable is added %00*%00 to mark it as protected.

5.1.2.2. PHP Deserialization Vulnerability

PHP will call __wakeup / __sleep etc function when deserializing, which may cause problems such as code execution. If there is no related function, the related destructor will also be called when destructing, which will also cause code execution.

Another two functions __toString / __call may also be used.

Among them , it is triggered when __wakeup deserializing, __destruct is triggered when GC is triggered, __toString is triggered when echo is triggered, and __call is triggered when an undefined function is called.

A simple demo is provided below:

class Demo
{

    public $data;

    public function __construct($data)
    {
        $this->data = $data;
        echo "construct<br />";
    }

    public function __wakeup()
    {
        echo "wake up<br />";
    }

    public function __destruct()
    {
        echo "Data's value is $this->data. <br />";
        echo "destruct<br />";
    }
}

var_dump(serialize(new Demo("raw value")));

output:

construct
Data's value is raw value.
destruct
string(44) "O:4:"Demo":1:{s:4:"data";s:9:"raw value";}"

After modifying the serialized string, execute:

unserialize('O:4:"Demo":1:{s:4:"data";s:15:"malicious value";}');

output:

wake up
Data's value is malicious value.
destruct

Here you can see that the value has been modified.

The above is a simple application of unserialize(),It is not difficult to see that if __wakeup() or __desturct() has sensitive operations, such as reading and writing files and operating databases, the behavior of file reading and writing or data reading can be realized through functions.

So, does adding a judgment in __wakeup() can prevent this vulnerability? In the __wakeup() we add a line of code

public function __wakeup()
{
    if($this->data != 'raw value') $this->data = 'raw value';
    echo "wake up<br />";
}

But in fact, it can still be bypassed. There are wakeup vulnerabilities in PHP5 < 5.6.25 and PHP7 < 7.0.10 versions. When the number of objects in deserialization is not equal to the previous number, wakeup will be bypassed, so the following payload is used:

unserialize('O:7:"HITCON":1:{s:4:"data";s:15:"malicious value";}');

output:

Data's value is malicious value.
destruct

Here wakeup is bypassed and the value is still modified.

5.1.2.3. Utilization points

5.1.2.3.1. SoapClient Original use

The SoapClient class in php can create soap data packets. In non-wsdl mode, when the instance of SoapClient is deserialized, it will make a soap request to the url specified by the second parameter. This feature can be used for SSRF.

5.1.2.3.2. ZipArchive Original use

If the flag parameter in the php native class ZipArchive::open() is set to ZipArchive::OVERWRITE, the specified file will be deleted. This feature can be used to delete files under certain conditions.

5.1.2.3.3. Session

Sessions in PHP are stored as files by default. The files are named with sess_sessionid. When the session is controllable to a certain extent, deserialization can be triggered through the session.