[CTF]PHP deserialization summary

brief introduction
Serialization is actually transforming data into a reversible data structure. Naturally, the reverse process is called deserialization.

Find a more vivid example on the Internet

For example, now we all buy tables on Taobao. How can we transport such irregular things from one city to another? At this time, we usually tear them down into boards and put them in boxes, so that they can be sent by express, This process is similar to our serialization process (converting data into a form that can be stored or transmitted). When the buyer receives the goods, he needs to assemble these boards into a table. This process is like a reverse sequence process (converting into the original data object).

php uses two functions to serialize and deserialize data

serialize formats objects into ordered strings

unserialize restores the string to the original object

The purpose of serialization is to facilitate data transmission and storage. In PHP, serialization and deserialization are generally used as caching, such as session caching, cookie s, etc.

Common serialization formats
Just understand

Binary format
Byte array
json string
xml string
Case introduction
Simple example (take array as an example)

<?php
$user=array('xiao','shi','zi');
$user=serialize($user);
echo($user.PHP_EOL);
print_r(unserialize($user));

He will output

a:3:{i:0;s:4:"xiao";i:1;s:3:"shi";i:2;s:2:"zi";}
Array
(
    [0] => xiao
    [1] => shi
    [2] => zi
)

Let's give a brief explanation of the above example to facilitate you to get started

a:3:{i:0;s:4:"xiao";i:1;s:3:"shi";i:2;s:2:"zi";}
a:array Represents an array. The following 3 descriptions have three properties
i:Represents integer data int,The following 0 is the array subscript
s:Represents a string, followed by 4 because xiao Length 4
    
And so on

The serialized content has only member variables and no member functions, such as the following example

<?php
class test{
    public $a;
    public $b;
    function __construct(){$this->a = "xiaoshizi";$this->b="laoshizi";}
    function happy(){return $this->a;}
}
$a = new test();
echo serialize($a);
?>

Output (O stands for Object, which means Object and class)

O:4:"test":2:{s:1:"a";s:9:"xiaoshizi";s:1:"b";s:8:"laoshizi";}

If the variable is protected, then \ x00 * \ x00 will be added before the variable name, and private will add \ x00 class name \ x00 before the variable name. url encoding is generally required for output. base64 encoding is recommended for local storage, as follows:

<?php
class test{
    protected  $a;
    private $b;
    function __construct(){$this->a = "xiaoshizi";$this->b="laoshizi";}
    function happy(){return $this->a;}
}
$a = new test();
echo serialize($a);
echo urlencode(serialize($a));
?>

Output will result in the loss of the invisible character \ x00

O:4:"test":2:{s:4:" * a";s:9:"xiaoshizi";s:7:" test b";s:8:"laoshizi";}

Common magic methods in deserialization
__ wakeup() / / this function will be called first when executing unserialize()
__ sleep() / / this function will be called first when you execute serialize()
__ Destroy() / / triggered when the object is destroyed
__ call() / / triggers in an object context when an invocable method is invoked.
__ callStatic() / / triggering an invocable method in a static context
__ get() / / used to read data from inaccessible properties or call this method if this key does not exist
__ set() / / used to write data to inaccessible attributes
__ Isset() / / triggered by calling isset() or empty() on an inaccessible property
__ Unset() / / triggered when unset() is used on an inaccessible property
__ toString() / / triggered when a class is used as a string
__ invoke() / / triggered when an attempt is made to call an object as a function
 

Deserialization bypasses little Trick

php7.1 + deserialization is not sensitive to class attributes

As we said earlier, if the variable is protected, the serialization result will add \ x00*\x00 before the variable name

However, in a specific version above 7.1, it is not sensitive to class attributes. For example, the following example will still output abc even without \ x00*\x00

<?php
class test{
    protected $a;
    public function __construct(){
        $this->a = 'abc';
    }
    public function  __destruct(){
        echo $this->a;
    }
}
unserialize('O:4:"test":1:{s:1:"a";s:3:"abc";}');

 

Bypass__ wakeup(CVE-2016-7124)

edition:

​ PHP5 < 5.6.25

​ PHP7 < 7.0.10

Utilization method: when the value representing the number of object attributes in the serialized string is greater than the actual number of attributes, it will be skipped__ Implementation of wakeup

For the following custom class

<?php
class test{
    public $a;
    public function __construct(){
        $this->a = 'abc';
    }
    public function __wakeup(){
        $this->a='666';
    }
    public function  __destruct(){
        echo $this->a;
    }
}

If you execute unserialize('O:4:"test":1:{s:1:"a";s:3:"abc";} '); The output is 666

Increase the value of the number of object attributes and execute unserialize('O:4:"test":2:{s:1:"a";s:3:"abc";} '); The output result is abc

Bypass partial regularization
preg_match('/^O:\d + /') matches whether the serialized string starts with the object string, which has been a similar test point in CTF

Bypass by using the plus sign (note that + should be encoded as% 2B when passing parameters in the url)
serialize(array(a ) ) ; / / a));//a));//a is the object to be deserialized (the serialization result starts with a and does not affect the destruct of $a as an array element)
 

<?php
class test{
    public $a;
    public function __construct(){
        $this->a = 'abc';
    }
    public function  __destruct(){
        echo $this->a.PHP_EOL;
    }
}

function match($data){
    if (preg_match('/^O:\d+/',$data)){
        die('you lose!');
    }else{
        return $data;
    }
}
$a = 'O:4:"test":1:{s:1:"a";s:3:"abc";}';
// +Number bypass
$b = str_replace('O:4','O:+4', $a);
unserialize(match($b));
// serialize(array($a));
unserialize('a:1:{i:0;O:4:"test":1:{s:1:"a";s:3:"abc";}}');

Use reference

<?php
class test{
    public $a;
    public $b;
    public function __construct(){
        $this->a = 'abc';
        $this->b= &$this->a;
    }
    public function  __destruct(){

        if($this->a===$this->b){
            echo 666;
        }
    }
}
$a = serialize(new test());

In the above example, setting $b as a reference to $a can make $a equal to $b forever

Hexadecimal bypass character filtering

O:4:"test":2:{s:4:"%00*%00a";s:3:"abc";s:7:"%00test%00b";s:3:"def";}
Can be written as
O:4:"test":2:{S:4:"\00*\00\61";s:3:"abc";s:7:"%00test%00b";s:3:"def";}
That represents the character type s When capitalized, it will be parsed as hexadecimal.

I wrote an example here

<?php
class test{
    public $username;
    public function __construct(){
        $this->username = 'admin';
    }
    public function  __destruct(){
        echo 666;
    }
}
function check($data){
    if(stristr($data, 'username')!==False){
        echo("You can't get around!!".PHP_EOL);
    }
    else{
        return $data;
    }
}
// Before treatment
$a = 'O:4:"test":1:{s:8:"username";s:5:"admin";}';
$a = check($a);
unserialize($a);
// After processing, \ 75 is hexadecimal of u
$a = 'O:4:"test":1:{S:8:"\\75sername";s:5:"admin";}';
$a = check($a);
unserialize($a);

 

PHP deserialization character escape

Case 1: more characters after filtering

First, the local php code is given. It is very simple. Without too much explanation, it is to replace one x after deserialization with two

<?php
function change($str){
    return str_replace("x","xx",$str);
}
$name = $_GET['name'];
$age = "I am 11";
$arr = array($name,$age);
echo "Deserialize string:";
var_dump(serialize($arr));
echo "<br/>";
echo "After filtration:";
$old = change(serialize($arr));
$new = unserialize($old);
var_dump($new);
echo "<br/>At this point, age=$new[1]";

Normally, name=mao is passed in

Let's first look at the results and then explain

We pass in name = maoxxxxxxxxxxxxxxxxxx "; I: 1; s: 6:" Woaini ";}
"; i:1;s:6:"woaini ";} This part has twenty characters in total
Since one X will be replaced by two, we have entered a total of 20 x, now it is 40. The extra 20 x actually replace our 20 characters "; i:1;s:6:"woaini ";}, resulting in"; i:1;s:6:"woaini";} And "closes the previous string, making our string escape successfully, which can be deserialized and output Woaini
Last;} Closing the whole process of deserialization causes the original "; i:1;s:7:"I am 11 ";}" to be discarded without affecting the deserialization process

<?php
function change($str){
    return str_replace("xx","x",$str);
}
$arr['name'] = $_GET['name'];
$arr['age'] = $_GET['age'];
echo "Deserialize string:";
var_dump(serialize($arr));
echo "<br/>";
echo "After filtration:";
$old = change(serialize($arr));
var_dump($old);
echo "<br/>";
$new = unserialize($old);
var_dump($new);
echo "<br/>At this point, age=";
echo $new['age'];

Keywords: PHP

Added by beginneratphp on Thu, 23 Dec 2021 20:29:00 +0200