好文速推我看过且对我有很大帮助的https://xz.aliyun.com/news/11953https://blog.csdn.net/2302_80420483/article/details/156270205https://blog.csdn.net/2301_80913334/article/details/136880866前置知识在深入原理之前我们先明确几个关键概念1.序列化 (Serialization): 将一个PHP对象只包含其类名和属性值不包含方法转换成一个可存储或传输的字符串的过程使用 serialize() 函数。2.反序列化 (Unserialization): 将序列化后的字符串重新转换回一个PHP对象的过程使用 unserialize() 函数。即反序列化会创建对象3.魔术方法 (Magic Methods): 以双下划线 __ 开头的特殊方法。它们会在特定的事件或操作时被PHP自动调用而不是由开发者显式调用。在反序列化漏洞中__wakeup(), __destruct(), __toString() 等方法是关键。4.POP链 (Property-Oriented Programming Chain): 攻击者通过精心构造序列化字符串将多个类的魔术方法和普通方法串联起来形成一条“利用链”最终达到执行任意代码的目的。常见魔法函数__wakeup()//执行unserialize()时反序列化之后立即调用这个函数__sleep()//执行serialize()时先会调用这个函数__destruct()//对象被销毁时触发对象执行完自动销毁程序结束或引用被删__call()//在对象上下文中调用不可访问的方法时触发 ($obj-nonexist())__callStatic()//在静态上下文中调用不可访问的方法时触发__get()//用于从不可访问的属性读取数据或者不存在这个键都会调用此方法($obj-undefined)__set()//用于将数据写入不可访问或不存在的属性 ($obj-a 1)__isset()//在不可访问的属性上调用isset()或empty()触发__unset()//在不可访问的属性上使用unset()时触发__toString()//把类当作字符串使用时触发echo, print, 字符串拼接__invoke()//当尝试将对象调用为函数时触发 ($obj())__construct()//当使用 new 关键字实例化一个对象时构造函数将会自动调用类的实例化执行过程阶段一对象重建与属性注入类的实例化1.解析字符串: unserialize() 函数接收字符串并按照PHP序列化的特定格式进行解析。例如O:4:“User”:2:{s:4:“name”;s:5:“Alice”;s:3:“age”;i:25;} 会被解析为一个名为 User 的类包含两个属性 name 和 age。2.实例化对象: PHP会根据解析出的类名 User立即实例化一个该类的对象。这个过程会为对象分配内存。3.属性赋值: PHP会将序列化字符串中保存的属性值直接“注入”到新创建的对象属性中。关键点在于这个赋值过程绕过了构造函数 __construct()。这意味着即使类中定义了构造函数它在反序列化时也不会被调用。攻击者正是利用这一点可以为对象的属性赋予任意值从而影响后续代码的执行流程。阶段二魔术方法的自动触发对象重建完成后PHP会根据上下文环境自动调用相应的魔术方法。这是反序列化漏洞利用的核心环节。pop链其实没有多高大上就是单个类的魔术方法可能不足以完成复杂的攻击。POP链的思想就是将多个类像链条一样连接起来最终指向我们要利用的函数类值的传递局部变量与对象属性的区别$free_flag我是局部/全局变量;// 存在当前符号表局部/全局$target-free_flag我是对象属性;// 存在对象内部属性表// 即使都叫 free_flag也是存在两个不同的盒子就不是一个东西中注意$target-free_flag 我是对象属性;的含义为在target对象内部属性表中指向free_flag 并把 我是对象属性赋值到free_flag的属性不同可访问性的类值序列化后格式不同可访问性序列化后的属性名格式实际长度计算关键示例类Test属性namepublic属性名属性名长度s:4:name→ 长度是4private%00类名%00属性名属性名长度 类名长度 2两个%00各占1字节s:10:%00Test%00name→ 44210protected%00*%00属性名属性名长度 3两个%00 一个*s:7:%00*%00name→ 437比如classFLAG{public$public_flagA;protected$protected_flagB;functionget_protected_flag(){return$this-protected_flag;}$targetnewFLAG();eval(XXX);eval();就是从类外部访问所以eval(echo $target-public_flag);可以得到A但eval($target-protected_flag);得不到B。想要得到B根据protected只有类自身和子类可以访问的特性需要先用eval();调用FLAG()中的函数类内部的函数通过类内部的函数访问protected_flag就可以访问成功了因此构造eval(echo $target-get_protected_flag(););才可以得到B传参中的小技巧private 和 protected 属性序列化后包含 0x00 字节在 URL 传参、HTTP Header、部分 WAF 中会被截断或过滤// 原始序列化字符串含不可见字符O:4:test:1:{s:9:\0test\0age;s:2:20;}// 直接 URL 编码后传入正确做法O%3A4%3A%22test%22%3A1%3A%7Bs%3A9%3A%22%00test%00age%22%3Bs%3A2%3A%2220%22%3B%7D// 错误直接粘贴到 URL\0 被截断O:4:test:1:{s:9:[截断];...}技巧本地生成 Payload 后务必用 urlencode() 或 base64_encode() 编码再传输尤其涉及 private/protected 属性时序列化规则classa_class{public$a_valueHelloCTF;}$a_objectnewa_class();$a_arrayarray(aHello,bCTF);$a_stringHelloCTF;$a_number678470;$a_booleantrue;$a_nullnull;See How to serialize:a_object:O:7:a_class:1:{s:7:a_value;s:8:HelloCTF;}a_array:a:2:{s:1:a;s:5:Hello;s:1:b;s:3:CTF;}a_string:s:8:HelloCTF;a_number:i:678470;a_boolean:b:1;a_null:N;O类a数组s字符串i数字btrue1fales0NnullReflectionClassPHP 反射绕过访问控制积累CVE -2016-7124__wakeup()绕过漏洞当目标类存在__wakeup()​方法反序列化时自动触发可能篡改恶意属性需通过该CVE漏洞绕过直接执行__destruct()​中的危险代码。漏洞核心信息漏洞本质PHP反序列化时若序列化字符串中声明的“属性个数”大于类实际的属性个数会直接跳过__wakeup()​的执行影响版本PHP 5.x 5.6.25PHP 7.x 7.0.10利用条件1.目标使用unserialize()​且参数可控2.目标类定义了__wakeup()​存在拦截行为3.类中存在__destruct()​等其他可利用的魔术方法含危险操作。实战极客大挑战2019