从CTF刷题记录对php反序列化的总结
字数 2524 2025-12-19 12:32:20
PHP反序列化漏洞全面解析
1. 序列化与反序列化基础
1.1 基本概念
序列化是将对象中的数据(对象属性)转换为便于传输和保存的形式的过程。反序列化是其逆向过程,将序列化后的数据还原为对象。
示例理解:网上购物时,商家将桌子拆卸成易于运输的零件(序列化),用户收到后重新组装(反序列化)。
1.2 序列化字符串结构分析
O:1:"a":1:{s:1:"b";s:1:"1";}
O:表示对象类型1:对象名称长度a:对象名称1:成员属性数量s:1:"b":字符串类型,长度1,属性名"b"s:1:"1":字符串类型,长度1,属性值"1"
常见类型表示:
b:布尔值a:数组i:整数d:浮点型s:字符串S:十六进制字符串O:对象
2. PHP访问修饰符与序列化
2.1 三种访问修饰符
- public:所有类可访问,序列化格式:
s:1:"b" - private:仅当前类可访问,序列化格式:
s:4:"%00a%00b" - protected:当前类及子类可访问,序列化格式:
s:7:"%00*%00b"
2.2 PHP版本差异
PHP 7.x+对访问修饰符不敏感,可将private/protected替换为public简化调用链。
3. 常用魔术方法
| 魔术方法 | 触发条件 |
|---|---|
__construct |
对象实例化时触发 |
__destruct |
对象销毁时触发 |
__toString |
对象被当作字符串调用时触发 |
__invoke |
对象被当作函数调用时触发 |
__set |
给不存在的属性赋值时触发 |
__call |
调用不存在的方法时触发 |
__isset |
使用isset()或empty()时触发 |
__clone |
对象被克隆时触发 |
__sleep |
serialize()时触发 |
__wakeup |
unserialize()时触发 |
4. CTF中的攻击方法与绕过技巧
4.1 签到题类型
特征:直接按顺序实例化即可RCE或文件读取
解决方法:分析调用链,直接实例化相关类
4.2 入门级别题目
特征:调用链混乱,多个类中存在迷惑方法
解决方法:仔细分析每个类的方法作用,构造合理调用链
4.3 小白级别题目:绕过技巧
4.3.1 绕过__wakeup()
条件:PHP < 5.6.25 或 PHP 7 < 7.0.1
方法:修改序列化字符串中属性值个数大于实际属性个数
O:1:"a":1:{s:1:"b";s:1:"1";} # 原版
O:1:"a":2:{s:1:"b";s:1:"1";} # 绕过版
4.3.2 绕过__destruct()
方法一:利用GC回收机制
- 对象被unset()处理时可触发
- 数组对象为NULL时可触发
方法二:删除序列化字符串末尾的右括号
4.3.3 关键字绕过
- 加号绕过:利用加号被解释为空格破坏正则匹配
- 十六进制绕过:使用
S代替s进行十六进制编码
# 示例:S:4:"\78\79\7a\61" 对应 "xyza"
4.4 进阶级别题目
4.4.1 Session反序列化
Session处理器对比:
| 处理器 | 存储格式 |
|---|---|
| php | 键名+竖线+serialize()值 |
| php_binary | 长度ASCII+键名+serialize()值 |
| php_serialize | serialize()处理的数组 |
攻击手法:Session反序列化 + CRLF + SSRF
利用类:SoapClient::__call()
4.4.2 字符串逃逸
增多逃逸:替换后字符串长度增加
减少逃逸:替换后字符串长度减少
4.4.3 使用Error/Exception类绕过哈希比较
# 利用__toString方法绕过md5/sha1比较
$a = new Error("payload", 1);
$b = new Error("payload", 2);
# $a和$b不同,但__toString返回结果相同(除行号外)
4.4.4 文件读取原生类
FilesystemIteratorDirectoryIteratorGlobIterator
可搭配glob://协议和*通配符使用
4.4.5 C开头类绕过wakeup过滤
可用类:
ArrayObject::unserializeArrayIterator::unserializeRecursiveArrayIterator::unserializeSplDoublyLinkedList::unserializeSplQueue::unserializeSplStack::unserializeSplObjectStorage::unserialize
4.5 高级级别题目
4.5.1 Phar反序列化绕过
过滤phar关键字时的替代协议:
php://filter/read=convert.base64-encode/resource=文件名compress.bzip2://phar://文件名compress.zlib://phar://文件名
4.5.2 绕过__HALT_COMPILER检测
方法一:将Phar内容写入zip注释
$zip = new ZipArchive();
$zip->open('phar.zip', ZipArchive::CREATE);
$zip->addFromString('flag.txt', 'flag is here');
$zip->setArchiveComment($serialized_data);
$zip->close();
方法二:gzip压缩phar文件
gzip test.phar
修改签名脚本:
from hashlib import sha256
phar_path = '文件路径'
with open(phar_path, "rb") as f:
text = f.read()
main = text[:-40]
end = text[-8:]
new_sign = sha256(main).digest()
new_phar = main + new_sign + end
with open(phar_path, "wb") as f:
f.write(new_phar)
5. 常用函数总结
5.1 原生类相关
array_walk:对数组元素应用自定义函数,常伴随任意实例化create_function:创建匿名函数(PHP 7.2+已废弃)
5.2 命令执行相关
call_user_func:将第一个参数作为函数名调用
5.3 文件操作相关
file_put_contents:写入文件内容file_get_contents:读取文件内容,可搭配伪协议
6. 实战技巧总结
- 仔细分析调用链:理解每个魔术方法的触发条件
- 关注PHP版本差异:不同版本有不同特性和限制
- 灵活运用绕过技巧:根据具体过滤条件选择合适的绕过方法
- 善用原生类:Error/Exception等类在特定场景下很有用
- 组合利用技术:如Session反序列化+CRLF+SSRF的组合攻击
通过系统掌握这些知识点,能够有效应对从简单到复杂的PHP反序列化漏洞题目,在实际CTF比赛和安全测试中发挥重要作用。