一、定义
序列化(串行化):将变量转换为可保存或传输的字符串的过程(通常是字节流、JSON、XML格式)
反序列比(反串行化):把这个字符串再转化成原始数据结构或对象(原来的变量)使用
二、常见的php序列化格式
JSON:轻量级数据交换格式,易于阅读编写和机器的解析与生成
HML:标记语言,用于储存和传输数据
pickle:python模块,可将python对象序列化为二进制格式
Protocol Buffers(protobuf):可以高效地储存和交换数据结构的二进制序列化格式
三、基础知识
php面对对象编程:
对象:可以对其做事情的一些东西。一个对象有状态、行为和标识三种属性。
类:一个共享相同结构和行为的对象的集合。
每个类的定义都以关键字class开头,后面跟着类的名字。一个类可以包含有属于自己的变量,变量(称为“属性”)以及函数(“称为方法”)。类可能会包含一些特殊的函数叫magic函数,magic函数命名是以符号“_”开头的,比如_sleep,_wakeup等。这些函数在某些情况下会自动调用,比如:_construct当一个对象创建时调用(constructor);_destruct当一个对象被销毁时调用(destructor);_toString当一个对象被当作一个字符串时使用。
反序列化后对象里面的值与类里面预定义的值无关
private私有化属性,出现特殊字符要用%00来代替空,最后再用urldecode()进行解码,后进行反序列化
displayvar(),展示属性,必须要有预定义的类才能使用
漏洞成因:是因为unserialize()需要传参,而传入的参数可控,就可以产生漏洞
常见的php序列化和反序列化方式主要有:serialize,unserialize
1.serilaize()
用于序列化对象或数组,并返回一个字符串;序列化对象后,可以很方便的将它传递给其他需要它的地方,且其类型和结构不会改变
语法:string serialize ( mixed $value ) //$value是要序列化的对象或数组
eg.
<?php
$sites = array('Google', 'Runoob', 'Facebook');
$serialized_data = serialize($sites);
echo $serialized_data . PHP_EOL;
?>
输出为:
a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}
2.unserialize()
用于将通过serialize()函数序列化后的对象或数组进行反序列化,并返回原始的对象结构
语法:mixed unserialize ( string $str ) //$str是反序列化后的字符串
返回的是转换后的值,integer、float、string、array 或 object,如果传递的字符串不可解序列化,则返回 FALSE,并产生一个 E_NOTICE
eg.
<?php
$str = 'a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}';
$unserialized_data = unserialize($str);
print_r($unserialized_data);
?>
输出为:
a:3:{i:0;s:6:"Google";i:1;s:6:"Runoob";i:2;s:8:"Facebook";}
Array
([0] => Google[1] => Runoob[2] => Facebook
)
四、常见魔术方法
定义:在php类保留方法中以 “__”两个下划线开头的函数称为魔术方法,是一个预定义好的,在特定情况下自动触发的行为方法,这些函数可以在代码中任何地方不用声明就可以使用
- __construct(),类的构造函数,触发时机是实例化对象,用于提前清理不必要内容
- __destruct(),类的析构函数,对象的所有引用被删除或者对象被显示销毁时执行的魔术方法
- __call(),在对象中调用一个不可访问方法时调用
- __callStatic(),用静态方式中调用一个不可访问方法时调用
- __get(),获得一个类的成员变量时调用
- __set(),设置一个类的成员变量时调用
- __isset(),当对不可访问属性调用isset()或empty()时调用
- __unset(),当对不可访问属性调用unset()时被调用
- __sleep(),执行serialize()时,先会调用这个函数
- __wakeup(),执行unserialize()时,先会调用这个函数
- __toString(),类被当成字符串时的回应方法
- __invoke(),调用函数的方式调用一个对象时的回应方法
- __set_state(),调用var_export()导出类时,此静态方法会被调用
- __clone(),当对象复制完成时调用
- __autoload(),尝试加载未定义的类
- __debugInfo(),打印所需调试信息
五、绕过
1.常用手段
1>常见起点
__wakeup 一定会调用 //使用unserialize时触发
__destruct 一定会调用 //对象被销毁时触发
__toString 当一个对象被反序列化后又被当做字符串使用
2>常见中间跳板
__toString 当一个对象被当做字符串使用
__get 读取不可访问或不存在属性时被调用
__set 当给不可访问或不存在属性赋值时被调用
__isset 对不可访问或不存在的属性调用isset()或empty()时被调用
形如 $this-> $func();
3>常见终点
__call 调用不可访问或不存在的方法时被调用
call_user_func 一般php代码执行都会选择这里
call_user_func_array 一般php代码执行都会选择这里
2.protected和private绕过
变量前是protected,是\x00*\x00类名的形式
变量前是private,是\x00类名\x00的形式
方法:将protected改为public;手动将序列化后的形式改为protected或者private的标准形式,结合urlencode和base64编码进行操作
3.__wakeup绕过
当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup 的执行
比如将O:4:"Dino":1:{s:1:"a";s:4:"misc";}
改为O:4:"Dino":2:{s:1:"a";s:4:"misc";}
4.利用16进制绕过字符过滤
序列化结果:O:4:"Dino":1:{s:3:"way";s:3:"web";}
中含有字符web,但将s改成S后,O:4:"Dino":1:{S:3:"\\77ay";s:3:"web";}
利用十六进制绕过了字符的过滤检测
练习
[SWPUCTF 2021 新生赛]ez_unserialize
打开后找不到题目,查看源代码,得到提示是robots协议的语句
http://t.csdnimg.cn/tgwG8
可以看到出现新地址
访问后得到一串php代码;关闭了报错,显示cl45s.php中的内容,判断admin和passwd的值是否正确,如果admin===“admin",passwd==="ctf",就会输出flag.php,通过GET传参上传参数p,传递给unserialize()函数进行反序列化
构造url后得到flag
[SWPUCTF 2021 新生赛]no_wakeup
打开看到php源码;关闭报错,显示class.php中的内容,有个__wakeup()需要绕过,判断admin和passwd的值是否正确,如果admin===“admin",passwd==="wllm",就会输出flag.php,用GET传参传入p并且执行反序列化
构造url绕过__wakeup即可得到flag
[ZJCTF 2019]NiZhuanSiWei
打开发现一串php源码,查找发现需要使用php伪协议,运用data协议,并且需要base64绕过,传入welcome to the zjctf
用bp进行编码
传入url,还需要绕过一个正则匹配
构造url,可以得到一串base64编码
得出新的代码
原本的代码中还对password进行了反序列化,就将file=flag.php
进行序列化作为password的参数
构造url得到这个界面
查看源码即可得到flag