PHP反序列化
PHP序列化
在讲反序列化原理之前,先来一段php的序列化代码。
1 |
|
代码输出:
把输出贴上来看,这里面都是啥意思?我们一段一段来看。
1 | O:4:"test":3:{s:10:"testflag";s:6:"Active";s:8:"pub_flag";s:6:"public";s:11:"*pro_flag";s:7:"protect";} |
一个图来解释
问题来了,为啥长度为10呢?明明testflag只有8位啊?
我们把序列化的对象存起来(最后一行加上file_put_contents("serialize.txt", $data);
即可),再用hex friend
打开看看,会发现test
的前后是有00
的(00是空白符,所以复制出来的时候是看不到的)
长度的问题解决了,那为啥代码里没有testflag,序列化后里面就有呢?
这是因为php属性权限的问题,php序列化时会将属性的权限一并进行序列化。
-
public 权限
是几位就是几位,没啥变化。
-
private 权限
私有权限,会加上类名(如 testflag),序列化的格式是
%00类名%00属性名
-
protect 权限
不会加上类名,但是会加上
*
,用来标识反序列化时的属性权限为protect,序列化的格式是:%00*%00属性名
小结一下:
- PHP序列化时,只会序列化属性及其权限,不会序列化方法
- PHP序列化时,需要用到
serialize
方法(埋个伏笔,有惊喜)
PHP反序列化原理
我们来反序列化上面的serialize.txt
文件试试。
1 |
|
发现报错:
1 | /usr/local/opt/php@7.4/bin/php /Users/xxxx/PhpstormProjects/Learning/des_temp.php |
这是因为反序列化得到的类,不在当前类中。当更改反序列化代码之后:
1 |
|
可成功反序列化:
1 | /usr/local/opt/php@7.4/bin/php /Users/xxx/PhpstormProjects/Learning/des_temp.php |
因此,反序列化利用要注意:
- 反序列化的时候,要保证在当前的作用域下有该类的存在
- 要使用可控的类属性进行反序列化攻击
- unserialize方法的参数要可控
PHP反序列化—魔法方法
魔法方法的特性:
- 在类的序列化 或 反序列化时,会自动完成调用,不需要人工干预
因此,如果有我们可以用的魔法方法,就能打破「unserialize方法的参数要可控」的局限,进一步扩大攻击面。
常用的魔法方法:
-
__construct()
当对象创建的时候自动调用,但在unserialize的时候不会自动调用
-
__wakeup()
在反序列化之前,会检查是否具有
__wakeup()
魔术方法。如果存在该方法,则在反序列化时执行该方法。__wakeup()
魔术方法可以重构对象可能具有的任何资源。 -
__destruct()
对象被销毁的时候会自动调用。
-
__toString()
当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用
-
__get()
从不可访问的属性读取数据
-
__call()
在对象上下文中调用不可访问的方法时触发
-
__sleep()
执行serialize()时,先会调用这个函数,此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。
来举个序列化例子加深一下印象:
1 |
|
输出:
序列化时,会自动调用 __sleep
魔法方法
再来看看反序列化:
1 |
|
输出:
可以发现,在反序列化的时候,什么操作都没做,就会自动调用__wakeup
、__toString
、__destruct
魔法方法
使用魔法方法发起攻击
假如有这样一段代码:
1 |
|
可以看到, 类ro0t
里面有个魔法函数__destruct
,而类Evil
里面有个恶意函数system()
可以用来执行系统命令。现在我们来利用一下,看看怎么通过构造反序列化的代码,实现任意命令执行。
为了方便演示,我将从HTTP参数获取改为了字符串获取,以上代码的最后一行就变为:
1 | unserialize("O:4:\"ro0t\":2:{s:4:\"test\";O:4:\"Evil\":1:{s:5:\"test2\";s:2:\"id\";}s:4:\"flag\";s:4:\"flag\";}"); |
之后,我们再执行代码,可得到命令id
的输出:
你可能想问了,那个反序列化的串是怎么得到的?
其实很简单,根据以上代码,我们把最后一行改为:
1 | $root = new ro0t(); |
此时输出的$data
就是我们的payload的了。
图里之所以会执行命令id
,是由于代码$root->test->test2 = "id";
执行了,所以不用太纠结这部分输出。
至此,就演示了一下使用魔法方法发起反序列化攻击的方法。
还记得前面的伏笔吗?我们在「PHP序列化」的小结里提到:
PHP序列化时,需要用到
serialize
方法我们接下来解开一下这个伏笔。。。。
PHP序列化—伪协议
常规的PHP序列化需要用到serialize
方法,那骚操作呢?
答案就是:phar:// 伪协议
什么是phar?
phar(Php ARchive
)是PHP里类似jar的一种打包文件。在php > 5.3版本,phar后缀文件是默认开启支持的,可以直接使用。
phar文件的结构
-
存根(stub)
就是一个php文件,但是,必须以
__HALT_COMPILER();?>
结尾。 -
清单
由于phar是一种打包文件,里面存储着每个被压缩的文件、属性等信息,也就是这部分,会以序列化的形式存储用户自定义的
meta-data
,该部分也主要是用来进行反序列化攻击。 -
文件内容
想要压缩在phar内的文件
-
[可选]用来验证phar完整性的签名
为什么phar可以用来攻击反序列化?
php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化。
受影响的函数如下:
函数 | 函数 | 函数 | 函数 |
---|---|---|---|
fileatime | filectime | file_exists | file_get_conotents |
file_put_contents | file | filegroup | fopen |
fileinode | filemtime | fileowner | fileperms |
is_dir | is_executable | is_file | is_link |
is_readable | is_writable | is_writeable(is_writable别名) | parse_ini_file |
copy | unlink | stat | readfile |
还是举个例子,来加深一下印象:
先来生成一个phar文件
1 |
|
执行之后,可以看到phar.phar文件生成:
注意一下这个.metadata.bin
文件,内容是不是和之前的payload
很像?
再来利用phar文件进行反序列化利用
利用代码如下:
1 |
|
可成功执行payload
PHP反序列化利用Gadget
什么是POP?
POP 面向属性编程(Property-Oriented Programing) 常用于上层语言构造特定调用链的方法,是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链,最终达到攻击目的。
反序列化是通过控制对象的属性从而实现控制程序的执行流程,进而达成利用本身无害的代码进行有害操作的目的。
由于PHP序列化的特性(只序列化属性,不序列化方法),所以我们需要使用POP来挖掘利用的gadget
POP常规利用Gadget
总结一下前面讲的所有例子,大致流程如下:
- 先找有没有
unserialize()
函数,这个函数是否是我们可控的 - 寻找我们的反序列化的目标,重点寻找 存在
__wakeup()
或__destruct()
魔法函数的类 - 最后,一层一层地研究该类在魔法方法中使用的属性和属性调用的方法,看看是否有可控的属性能实现在当前调用的过程中触发
[待补充,__wakeup()
、__destruct()
的利用方式]
如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !