反序列化安全防御核心是流程控制而非参数拦截,需在反序列化前后及魔术方法中嵌入校验、白名单、深度限制等多层守卫。

在反序列化安全防御中,流程控制不是用来“拦截参数”本身,而是通过主动介入反序列化前后的执行链条,阻断恶意参数触发危险行为。关键不在于过滤字符串,而在于控制对象生命周期中的关键节点——类加载、魔术方法调用、属性赋值和资源释放。
在反序列化前强制校验输入结构
所有用户可控的序列化数据必须经过结构预检,不能直接进unserialize()或ObjectInputStream.readObject()。重点检查是否符合业务预期的格式特征:
- PHP场景:用正则快速识别非法类名,例如
preg_match('/^O:\d+:"[^"]+":/', $input)匹配对象声明头,再进一步验证类名是否在白名单内(如O:4:"Cart"合法,O:9:"BadGadget"立即拒绝) - Java场景:对字节流前4字节做魔数校验(
AC ED 00 05是标准Java序列化头),非此开头直接丢弃;若允许JSON替代,统一要求Content-Type: application/json且首字符为{或[ - 通用做法:提取序列化数据中的类标识字段(如JSON里的
"@type"、XML里的xsi:type),不在白名单内的一律返回400错误,不进入后续逻辑
在反序列化后立即扫描对象状态
即使类名合法,攻击者仍可能利用合法类中的漏洞属性触发RCE。因此需在对象还原完成后、业务逻辑使用前插入运行时检查:
- 递归遍历对象所有公开/受保护属性,查找含危险关键词的值(如
system(、exec(、file_put_contents、Runtime.getRuntime()等),发现即销毁对象并记录告警 - 对关键业务对象(如
UserSession、CacheItem)做字段白名单校验:只允许['id', 'token', 'expires_at']等明确字段存在,多余字段视为篡改,抛出异常 - 检查对象是否包含不可信的资源句柄(如
$_SERVER、$GLOBALS引用),防止通过反序列化污染全局上下文
在魔术方法中嵌入环境与权限守卫
__wakeup、__destruct、readObject等方法是攻击链必经出口,不能仅靠删除它们,而应把守卫逻辑写进这些方法内部:
- PHP的
__wakeup()开头加判断:if (!in_array(PHP_SAPI, ['cli', 'fpm'])) { throw new LogicException('Not allowed in web context'); } - Java的
readObject()里加入SecurityManager检查:System.getSecurityManager().checkPermission(new ReflectPermission("suppressAccessChecks"));,阻止反射绕过 - 所有
__destruct()方法第一行加if (!$this->_initialized) { return; },确保对象必须经由合法构造函数创建,而非反序列化直通
用中间层统一接管反序列化入口
避免在控制器、服务类里零散调用unserialize(),而是封装一个安全网关函数,集中管控所有反序列化行为:
- PHP示例:
safe_unserialize($data, ['allowed_classes' => ['Order', 'CartItem'], 'max_depth' => 3, 'max_string_length' => 10240]),内部自动做超深嵌套限制、字符串长度截断、类名比对、结果类型强制转换为数组 - Java示例:自定义
SafeObjectInputStream继承ObjectInputStream,重写resolveClass()方法,只允许加载java.lang.String、java.util.HashMap等基础类,其余一律抛ClassNotFoundException - 该中间层还应记录每次调用的来源IP、请求路径、序列化数据哈希,便于事后审计与异常模式识别











