序列化的作用
序列化是将对象的状态信息(属性)转换为可以存储或输出的形式的过程,php就是将对象或者数组转化为可存储/传输的字符串
常见序列化格式
类型 | 示例 | 格式 |
---|---|---|
空字符 | null | N; |
整形 | 666 | i:666; |
浮点型 | 66.6 | d:66.6; |
boolean型 | true | b:1; |
false | b:0; | |
字符串 | ‘benben’ | s:6:”benben”; |
数组
1 | <?php |
a指的是array,3是数组的成员数量,i后面的数字是数组成员编号
下面重点讲解对象的序列化
对象的序列化
1 |
|
序列化的是对象而不是类,对象是类的实例化
1 | O(object):4(类名长度):"test"(类名):1(变量数量):{s:3(变量名字长度):"pub"(变量名字);s:6(值的长度):"benben"(变量值);} |
默认只会序列化成员属性,不会序列化成员函数
注意:1、当成员属性是private私有属性序列化时在变量名前加”%00类名%00”,但空格在生成的poc中会变成小方块,所以在序列化时经常使用:
1 | echo urlencode(serialize($value)); |
进行url编码,使空格显示为%00
1 |
|
2、成员属性是protected受保护的进行序列化时在变量名前加”%00*%00”,变量名字长度同样要改变
1 |
|
3、成员属性调用实例化后的对象
1 |
|
反序列化漏洞的成因
首先需要知道的是发序列化生成的对象里的成员属性值是由反序列化里的值提供的,于原来类预定义的值无关。那么漏洞成因就是在反序列化过程中unserialize的字符串可控,通过更改这个字符串,反序列化后就可以得到所需要的代码,即生成的对象的属性值
做反序列化的题目,我们需要知道的是反序列化不改变类的成员方法,需要调用方法后才能触发
反序列化中常见的魔术方法
1 | _construct() //实例化对象 |
_destruct()触发
1、实例化对象,因为对象被创建最后用完一定会被销毁
2、反序列化,因为反序列化那串字符串后如果是个对象,一样后面会被销毁
计算机漏洞安全相关的概念POC 、EXP 、VUL 、CVE 、0DAY
https://blog.csdn.net/qq_37622608/article/details/88048847
字符串逃逸基础
在前面字符串没有问题的情况下,反序列化以;}结束,后面的字符串不影响正常的反序列化
属性逃逸
一般在数据先经过一次serialize在经过unserialize,在这个中间反序列化的字符串变多或者变少的时候才有可能存在反序列化属性逃逸
字符减少和字符增加
wakeup绕过
在反反序列化时,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup( )的执行。
影响版本
php5.0.0 ~ php5.6.25
php7.0.0 ~ php7.0.10
1 |
|
php语言的特性为在反序列化时,先执行__wakeup()魔术方法,才会执行__destruct()魔术方法
也就是说当我们使用payload
1 | class A{ |
反序列化时修改的$filename的值在__wakeup()函数时由flag.php修改为了test.txt
绕过__wakeup()函数时将对象属性个数的值大于真实的属性个数时即可绕过
即O%3A1%3A%22A%22%3A1%3A%7Bs%3A11%3A%22%00A%00filename%22%3Bs%3A8%3A%22flag.php%22%3B%7D只需要将对象个数大于1即可,2,3,4等等都行
引用的利用方式
session反序列化漏洞
当session_start()被调用或者php.ini中的session.auto_start为1时,PHP内部调用会话管理器,访问用户被序列化以后,储存到指定目录(默认为/tmp)
存取数据的格式有多种,常用的有三种
漏洞产生:写入格式和读取格式不一致
默认情况下用php格式储存
1 |
|
php:键名+竖线+经过serialize()函数序列化处理的值
声明session存储格式为php_serialize
1 |
|
php_serialize:经过serialize()函数序列化处理的数组
php反序列化例题:只截取其中的反序列化部分
[极客大挑战 2019]PHP 1
index.php中,文件包含class.php,下面对传入的参数进行反序列化,那么因为包含class.php,所以会触发里面的魔术方法
1 | include 'class.php'; |
class.php中,包含文件flag.php,所以有机会读取到从中读取到flag
1 | <?php |
构造pop链的关键在于wake_up的绕过
1 | <?php |
1 | O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D |
[NISACTF 2022]popchains
1 | <?php |
这题非常常规,就是魔术方法跳来跳去
[网鼎杯 2020 青龙组]AreUSerialz
1 | <?php |
定义的is_valid()函数检查每个字符是否在32和125之间,即是否是可打印字符,所以protected在序列化之后会出现不可见字符,不符合上面的要求,绕过方法就是直接改成public,原因是php7.1以上的版本对属性类型不敏感。
1 | private function read() { |
这里是我们需要关注的题目出口,file_get_contents()把整个文件读入一个字符串中,那么我们就可以将文件名改成我们要读取的文件这里可以看到上面的文件包含,想到了伪协议读取php://filter/read=convert.base64-encode/resource=flag.php
。接着要调用这个函数就关注到
1 | public function process() { |
当op==”2”时会调用read(),然后调用output
1 | private function output($s) { |
输出字符串内容
然后我们需要考虑如何调用process()
1 | function __destruct() { |
关注到这里,但是需要绕过强比较,所以op=2,既可以绕过强比较又可以让process中的弱比较返回true。__destruct()
会由实例化触发或反序列化触发。
构造pop链:
1 | <?php |
DASCTF EZUnserialize
这是一题字符减少
1 |
|
目标:通过file_get_contents输出$c的信息,所以可以使$c为flag.php
反推开始:1、触发__toString魔术方法
2、触发__destruct魔术方法,析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行,类B实例化最终被销毁的时候触发
poc:
1 |
|
这里需要对上面的源代码进行解释:$a = new A($_GET['a'],$_GET['b']);
将类A实例化并且接受传参,自动触发__construct(),并且参数会传入function __construct($a, $b)
1 | function __construct($a, $b){ |
补充:
详见:https://www.icoa.cn/a/957.html
回到正题,接下来将
再把序列后的值传给类A:
1 |
|
得到:
1 | O:1:"A":2:{s:8:"username";N;s:8:"password";s:55:"O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}";} |
传进去的序列化值,被当成字符串了。
而题目又给了两个方法:
1 | function write($data) { |
1 | 反序列化操作也给了出来: |
先试一下:
1 |
|
对比可以发现,通过read函数后,s:6:"*"
这段很明显是错误的,他包含到的是s:6:"*";s
,并且没有双引号闭合,如果要反序列化肯定是不行的(这里双引号里包含的是三个字符,浏览器显示问题看不到)。所以如果把一个特殊的的值赋值给password,然后通过read方法吞掉部分字符,就能达到字符串逃逸的效果
所以:
1 |
|
提前计算要逃逸的字符数";s:8:"password";s:73:"a
为23,由于23不能和3整除,所以添加一个字符a
payload:
1 | ?a=\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0&b=a";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}} |
对于涉及字符串逃逸的题还是有点懵,总是做着做着就忘记要干嘛了
[NISACTF 2022]babyserialize
反序列化绕过正则
preg_match(‘/^O:\d+/‘)
绕过方法1:利用加号来绕过过滤,因为数字1其实完整的写法是+1,所以这里我们就是在O后面这个数字前面加一个+
绕过方法2:在加号不能使用的情况下,我们可以使用数组绕过,在序列化的时候加上array
1 | //EXP |
1 | $a[]='flag.php'; |