为何PHP文档禁止向unserialize()传入不可信用户输入(即使allowed_classes=false)
allowed_classes=false后,unserialize()仍不能处理不可信用户数据? 你提到的点没错——当allowed_classes=true或不设置选项时,不可信数据会带来对象实例化的风险,比如触发魔术方法(__wakeup、__destruct)导致代码执行。但allowed_classes=false并非绝对安全,核心原因有这几个:
1. 拒绝服务(DoS)攻击风险
恶意构造的序列化字符串能直接拖垮服务器,和是否实例化对象无关:
- 比如嵌套深度达到数千层的数组,
unserialize解析时会消耗大量栈内存,直接导致栈溢出; - 或者包含重复的超大数据块,让PHP进程瞬间耗尽内存,引发OOM(内存不足)崩溃。
这类攻击不需要任何对象实例化,只要unserialize开始解析字符串就会触发。
2. 敏感信息泄露
当allowed_classes=false时,遇到对象类型的序列化数据,PHP会将其转为__PHP_Incomplete_Class实例,这个实例会保留原对象的类名和所有属性值。如果原序列化数据里包含敏感信息(比如数据库密码、用户隐私数据),这些内容会直接暴露在__PHP_Incomplete_Class的属性中,被攻击者获取。
3. 底层内存/资源漏洞
历史上PHP多个版本的unserialize函数存在底层漏洞,比如堆溢出、内存破坏等,这类漏洞的利用不需要实例化任何对象——只要构造特定格式的序列化字符串,就能触发内存错误,进而执行恶意代码或者破坏系统稳定性。即使allowed_classes=false,这些底层逻辑的漏洞依然存在。
4. 特殊内置类型的意外行为
部分PHP内置的特殊类型(比如资源句柄、闭包)在unserialize时,即使allowed_classes=false,也可能触发非预期的资源操作。比如旧版本中,某些资源类型的序列化数据被解析时,可能导致已打开的文件/网络连接被意外操作,或者资源句柄泄露。
总结来说,allowed_classes=false只是规避了对象实例化带来的代码执行风险,但unserialize本身的解析过程、数据结构处理依然存在被利用的空间。处理不可信数据的安全做法是:永远不要用unserialize处理用户输入,改用JSON、MsgPack等安全的序列化格式。
内容的提问来源于stack exchange,提问作者user2543740

