从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 三种访问修饰符

  1. public:所有类可访问,序列化格式:s:1:"b"
  2. private:仅当前类可访问,序列化格式:s:4:"%00a%00b"
  3. 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 文件读取原生类

  • FilesystemIterator
  • DirectoryIterator
  • GlobIterator
    可搭配glob://协议和*通配符使用

4.4.5 C开头类绕过wakeup过滤

可用类:

  • ArrayObject::unserialize
  • ArrayIterator::unserialize
  • RecursiveArrayIterator::unserialize
  • SplDoublyLinkedList::unserialize
  • SplQueue::unserialize
  • SplStack::unserialize
  • SplObjectStorage::unserialize

4.5 高级级别题目

4.5.1 Phar反序列化绕过

过滤phar关键字时的替代协议

  1. php://filter/read=convert.base64-encode/resource=文件名
  2. compress.bzip2://phar://文件名
  3. 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. 实战技巧总结

  1. 仔细分析调用链:理解每个魔术方法的触发条件
  2. 关注PHP版本差异:不同版本有不同特性和限制
  3. 灵活运用绕过技巧:根据具体过滤条件选择合适的绕过方法
  4. 善用原生类:Error/Exception等类在特定场景下很有用
  5. 组合利用技术:如Session反序列化+CRLF+SSRF的组合攻击

通过系统掌握这些知识点,能够有效应对从简单到复杂的PHP反序列化漏洞题目,在实际CTF比赛和安全测试中发挥重要作用。

PHP反序列化漏洞全面解析 1. 序列化与反序列化基础 1.1 基本概念 序列化 是将对象中的数据(对象属性)转换为便于传输和保存的形式的过程。 反序列化 是其逆向过程,将序列化后的数据还原为对象。 示例理解 :网上购物时,商家将桌子拆卸成易于运输的零件(序列化),用户收到后重新组装(反序列化)。 1.2 序列化字符串结构分析 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 方法 :修改序列化字符串中属性值个数大于实际属性个数 4.3.2 绕过__ destruct() 方法一 :利用GC回收机制 对象被unset()处理时可触发 数组对象为NULL时可触发 方法二 :删除序列化字符串末尾的右括号 4.3.3 关键字绕过 加号绕过 :利用加号被解释为空格破坏正则匹配 十六进制绕过 :使用 S 代替 s 进行十六进制编码 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类绕过哈希比较 4.4.4 文件读取原生类 FilesystemIterator DirectoryIterator GlobIterator 可搭配 glob:// 协议和 * 通配符使用 4.4.5 C开头类绕过wakeup过滤 可用类: ArrayObject::unserialize ArrayIterator::unserialize RecursiveArrayIterator::unserialize SplDoublyLinkedList::unserialize SplQueue::unserialize SplStack::unserialize SplObjectStorage::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注释 方法二 :gzip压缩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比赛和安全测试中发挥重要作用。