详谈CTF中常出现的PHP反序列化漏洞

访客4年前黑客文章1075

0x01什么是PHP序列化与反序列化

PHP序列化是一种把变量或对象以字符串形式转化以方便储存和传输的 ***

在PHP中,序列化用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构。

比方来说,我现在有一个类,我需要通过接口进行数据传输,或存储至数据库中以备将来使用,同时又想保持其结构,让接收方接收或数据库读数据时恢复其原来的结构,就需要通过序列化将对象按照一定格式转化为字符串的形式来进行传输

简单来说,就是用字符串储存数据,同时包含其数据类型以及结构信息

序列化函数:

PHP反序列化就是把被序列化的对象字符串转义为具体的对象。

反序列化函数:

举个简单的例子:

其中O代表object对象类型

4是类名的长度

"user"是类名

1是对象内属性的个数

s:8:"username"表示属性为字符串格式username为属性名 长度为8

s:3:"Bob"是属性的值

php中属性的共有类型共有三种:

被这三种修饰过的变量在序列化后有不同的格式

public 正常序列化

protected*变量名

private变量名

在复制payload时要注意手动输入

0x02与PHP反序列化有关的魔术 ***

什么是魔术 *** ?官方文档中是这么说的

__construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __serialize(), __unserialize(), __toString(), __invoke(), __set_state(), __clone()和 __debugInfo()等 *** 在 PHP 中被称为魔术 *** (Magic methods)。在命名自己的类 *** 时不能使用这些 *** 名,除非是想使用其魔术功能。

注意:所有的魔术 *** 必须声明为

警告:PHP 将所有以 __(两个下划线)开头的类 *** 保留为魔术 *** 。所以在定义类 *** 时,除了上述魔术 *** ,建议不要以 __ 为前缀。

在php反序列化利用中常常用到一下这些魔术 ***

这些 *** 都是在对象的生成、解析、序列化反序列化和调用时被调用。如果其中的属性可以被外部所利用,具有相当的危险性。

这些魔术 *** 的具体理解附在文章最后

0x0 *** HP反序列化漏洞

PHP反序列化漏洞又称PHP对象注入,主要成因就是在处理反序列化数据中处理不当导致的危险代码执行

举一个简单的例子

[NPUCTF2020]ReadlezPHP

类_HelloPhp_拥有两个魔术 *** ,在 *** 中,使用成员变量作为参数作为变量函数名执行代码

在代码最后接收GET参数并进行反序列化

这道题很简单,我们只需要通过构造与组成危险代码,然后构造HelloPhp类的序列化对象即可

构造payload:

0x04 __wakeup()函数绕过漏洞 <CVE-20167124>

影响版本

PHP5 < 5.6.25

PHP7 < 7.0.10

漏洞描述

当序列化字符串对象属性数量大于实际的属性数量时,将不会调用__wakeup函数

举个例子:

上述的例子中有一个score类,并且接受GET参数s。

我们假设这个例子中的s参数接受的是序列化的score对象,我们无论将属性name赋为什么值,结果都会被__wakeup函数给改为Bob。

image-20200323215532796

但是如果我们可以绕过__wakeup函数,就可以将name属性改成我们想要的任何值。

刚才我们传的参数s是

O:5:"score":2:{s:4:"name";s:4:"john";s:5:"score";s:4:"1000";}

改成:

O:5:"score":1:{s:4:"name";s:4:"john";s:5:"score";s:4:"1000";}

即可实现控制输出的内容,从而绕过__wakeup函数

image-20200323224822512

0x05 同名函数利用

看下面这个例子:

在这个例子中class B和class C有一个同名 *** ,class A中在__construct时创建了一个class B并在__destruct时调用了其action *** ,我们可以构造目标对象,使得解构函数调用class C的action *** ,实现任意代码执行。

0x06 PHP Session反序列化漏洞

在php中session值经序列化后储存,读取时再进行反序列化操作

在php.ini中有这么几项配置:

serialize_handler的值决定了php储存session数据的方式,共有三种:

serializer实现 ***
php键名 + 竖线 + 经过 serialize() 函数反序列处理的值
php_binary键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值
php_serialize(php>5.5.4)把整个数组作为一个数组序列化

session序列化后需要储存在服务器上,默认的方式是储存在文件中,储存路径在中,如果没有规定储存路径,那么默认会在储存在中,文件的名称是’sess_’+session名,文件中储存的是序列化后的session。

php储存session的示例:

访问该页面后

image-20210115112527454

session储存在下,文件名为+,文件内容为session数据

image-20210115113229927

image-20210115114403654

这是模式的session值

image-20210115140545281

这是模式的session值

image-20210115140644663

这是模式的session值

Php session反序列化产生的漏洞就在于同一服务中session处理器设置()出现了不同一

通过如下的例子理解:

在test1.php页面中有一个危险类A ,序列化处理器为php

在session.php中接受用户传入的参数并储存至session中,序列化处理器为php_serialize

先来看生成payload:

payload为

我们以此为参传入

image-20210115152030856

image-20210115152015819

由于session.php的序列化处理器为php_serialize,因此储存至文件中的序列化数据形成了如上图的内容,而当被test1.php使用反序列化处理器php读取时,由于读取格式的不同 竖线前的内容将会被作为键值读取,而之后的内容会被作为序列化数据从而被反序列化:

image-20210115152448653

因此在访问test1.php页面时引发了反序列化的session值产生了一个危险类A的对象并执行了危险代码:

image-20210115152557971

0x07 php反序列化字符串逃逸

基础思想

在理解了php反序列化后你就能知道字符串逃逸本质上是闭合,类似于sql注入中利用分号或者引号来闭合语句。反序列化字符串逃逸也是利用php对序列化字符串解析的特性来进行攻击。

在反序列化时,序列化的值是以分号作为分隔,在结尾以}为结束。我们看一个例子:

运行结果:

反序列化的过程就是当碰到与{最接近的;}时完成匹配并停止解析,我们将上列反序列化结果的结尾加上一些无意义的字符串并反序列化。

经过反序列化后:

image-20201125195730481

可以看到我们在结尾添加的123123并没有影响反序列化,程序没有报错解析结果也没有问题。这说明上述的原理是正确的。

但是当我修改字符串长度的数值时,我们来看看反序列化还能否正常解析,例如我将改为:

php报错:

实际上,在解析字符串的过程中,当字符串长度与反序列化描述不同时php将报错,换一种说法:php将按照反序列化字符串定义的长度读取字符串,当引号内的定义长度的字符串在读取时未读取至末尾引号之前的字符、或超越了末尾引号进行读取时就会报错。

因为这种报错的存在,字符串逃逸分为两种:

1.关键字增多

2.关键字减少

关键字增多

在题目中,往往对一些关键字会进行一些过滤,使用的手段通常替换关键字,使得一些关键字增多,简单认识一下,正常序列化查看结果。

输出:

在上述代码中,未反序列化的序列化字符串中的所有o将被替换为oo,这必然破坏了序列化字符串原有的规则,因而必然引起报错,而我们的目的就是构造字符串使其不要引起报错,为了更易理解,我们增加一个需求:age字段修改为35。

image-20201126092613823

Payload:

原理:在经过preg_replace以后,每个o都会被替换为oo,比如说我们传入会被替换为,这样必然报错。我们要做的就是让这个字符串在增长以后真的有它描述的那么长,同时达到我们将age字段修改为35的目的。

而实现这一目的的 *** 就是**使反序列化结构包含在这一字符串内,当字符串经过替换后,使其长度等于原来包含反序列化结构的字符串长度,从而使之正常被作为字符串解析的同时使包含在字符串内的反序列化结构被php识别为反序列化结构进行解析,同时冗余的反序列化结构(也就是完整{;}结构以外的)被遗弃。**可能乍一看难以理解,可以选择再仔细理解一下基础思想或继续向下看构造payload的每一个过程。

我们还以上述代码为例,我们需要在字符串中构造一个反序列化结构同时还要达到在替换后闭合前侧字符串的目的:,而这一反序列化结构的长度是22:

image-20201126154657931

也就是说我们需要22个字符来代替其位置,每一个o会被替换为oo也就是增加一个字符,也就是说我们需要22个o来增加22个字符的长度。

因此我们将22个字符长度的与组成username变量的值,我们将其序列化后得到:

在被preg_replace替换后,长度为22的变为长度为44的,整个反序列化的payload也变为:

当PHP反序列化这个payload时将被作为整体解析,而在最前面的引号与分号闭合了前面的变量后的也会被正常解析。而因为已经满足了完整的结构将被遗弃而不被解析。

因而可以看到如下的反序列化结果:

这就完成了一次成功的‘逃逸’。

关键字减少

如果上述内容理解的还可以,那么关键字减少的部分理解起来不会很难。和关键字增多的区别在于通常会把需要反序列化的结构放在关键字增多中冗余抛弃的那一部分,而关键字增多这一部分中我们希望通过关键字符串增多而解析的反序列化结构在关键字减少中需要因字符串长度变化而成为关键字符串的一部分从而不被解析。

和关键字增多的例子相似:

在这一个例子里oo会被替换为o,假设我们的目的仍然是将age的值改为35。

payload:

image-20201126193229151

可以看到age被改为了35。

实现这一目的的原理和关键字增多类似:长度是22,我们构造44个o来作为username的值,然后将我们需要更改的反序列化结构放在最后面,经过preg_replace替换后o的数量变为22,而username所定义的长度为44,这时php会将这22个字符一并作为username的一部分进行读取,而后我们后面部分只要闭合的好,就会被作为正常的反序列化结构进行解析,从而达到逃逸的目的。

0x08 Phar反序列化漏洞

Phar (“Php ARchive”) 是PHP里类似于JAR的一种打包文件。如果你使用的是 PHP 5.3 或更高版本,那么Phar后缀文件是默认开启支持的,你不需要任何其他的安装就可以使用它。而Phar文件中也存在反序列化的利用点:phar文件会以序列化的形式储存用户自定义的meta-data,在执行Phar文件时meta-data中用户自定义的元数据将被反序列化从而达到反序列化攻击的目的,这也扩展了PHP反序列化攻击的攻击面

这一利用出自2018年Blackhat大会上的Sam Thomas分享的File Operation Induced Unserialization via the「phar://」Stream Wrapper这个议题,具体可以看这里【传送门】。(From Freebuff)

该 *** 在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以
不依赖unserialize()直接进行反序列化操作。

在CTF中常结合文件上传,任意文件读,反序列化POP链构造考察 难度中高

Phar文件的文件结构:

1. a stub

可以理解为一个标志,格式为,前面内容不限,但必须以来结尾,否则phar扩展将无法识别这个文件为phar文件。

2. a manifestdescribing the contents

phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。

img

3. the file contents

被压缩文件的内容。

4. [optional] a signaturefor verifying Phar integrity (phar file format only)

签名,放在文件末尾,格式如下:

img

demo

根据文件结构我们来自己构建一个phar文件,php内置了一个Phar类来处理相关操作。

注意:要将php.ini中的选项设置为,否则无法生成phar文件。

通过hex可以看到数据确实是经序列化储存在文件中

image-20210115174601060

有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:

img

看一下PHP的底层逻辑:

php-src/ext/phar/phar.c

img

通过一个小例子验证刚才生成的phar文件

image-20210115175450771

其他文件操作函数也是可行的:

image-20210115175618727

当文件系统函数的参数可控时,我们可以在不调用unserialize()的情况下进行反序列化操作,一些之前看起来“人畜无害”的函数也变得“暗藏杀机”,极大的拓展了攻击面。

将phar伪造成其他格式的文件

在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切一点来说是这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。

image-20210115175843312

附:反序列化漏洞中常出现的php魔术函数

1.__construct()

实例化对象时被调用, 当__construct和以类名为函数名的函数同时存在时,__construct将被调用,另一个不被调用。

注意:通过反序列化产生的对象并不会调用__construct函数

2.__destruct()

当删除一个对象或对象操作终止时被调用。常用

3.__call()

对象调用某个 *** , 若 *** 存在,则直接调用;若不存在,则会去调用__call函数。

4.__get()

读取一个对象的属性时,若属性存在,则直接返回属性值; 若不存在,则会调用__get函数。

5.__set()

设置一个对象的属性时, 若属性存在,则直接赋值;
若不存在,则会调用__set函数。

6.__toString()

打印一个对象的时被调用。如echo obj;或printobj;或printobj;

7.__clone()

克隆对象时被调用。如:t=newTest();t=newTest();t1=clone $t;

8.__sleep()

serialize之前被调用。若对象比较大,想删减一点东东再序列化,可考虑一下此函数。

9.__wakeup()

unserialize时被调用,做些对象的初始化工作。

参考链接:

https://paper.seebug.org/680/

https://www.cnblogs.com/ichunqiu/p/10484832.html

相关文章

黑客人商,黑客可以查到微信删除的聊天记录吗,黑客可以找微信密码不

{6.29#并发履行的进程数量咱们观察到数据库信息/_layouts/15/error2.aspx那么,寻觅此类绕过缝隙的中心便是寻觅注入新Cookie的办法,看过一些事例,我概括出来几种: 这也阐明...

外围经纪,高端外围经纪人预约微信

外场模特经纪人 针对为每一个外场模特经纪人而言,我觉得许多 的经纪人全是给自己的商务承担的,她们为了更好地可以协助这种商务获得一系列的发展趋势水准得话,她们也会竭尽所能的找寻各种資源,为这种商家店铺女...

找微信密码神器下载2020(找微信密码黑客软件手机版)

盗微信密码黑客软件手机版(盗微信密码神器下载2020)有些人的技术非常高,是可以盗取别人的微信密码的,所以在设置密码的时候,要尽量多设计一些。真的能盗微信密码吗感谢您你好,请继续“追问”。微信密码破译...

3室1厅(3室1厅1卫120平)

可能是你听错了吧现在一般开发的房子都是两厅的所谓的两厅其实就是指餐厅和客厅,很多时候人看到户型的时候都会理解错误,看到的一个大厅以为是一个厅。 你可以找专业的人士咨询下的,合肥这边就有家大金空调和东芝...

黑客查定位(黑客查询定位)

黑客查定位(黑客查询定位)

本文导读目录: 1、问一下,有朋友找黑客追踪定位过手机号吗?会不会被骗啊? 2、黑客能不能查到关闭数据网络的手机号? 3、黑客可以根据手机号码确定位置吗? 4、厉害的黑客该如何躲避网警的追...

合同审批流程应该如何设计

合同审批流程应该如何设计

公司的策划勾当离不开条约,条约的签订离不开审批,抛开审批谈条约打点将显得很空洞,那么一个条约审批流程应该如何设计将会显得很重要,那么条约审批流程应该如何设计。 一、审批流程简介 在企业内部,一条审批...