序列化的原因。我记得好像是解决传输问题,加上不让你看懂。


学了开发之后,这些太简单了。
.
反序列化可以覆盖属性,利用原有的东西触发函数。
反序列化只能调用有的代码对不对,但是为什么可以修改原来内里面的属性?是不是相当于创建实例的时候直接修改?
反序列化的核心功能不是"修改",而是"还原对象状态"------"修改属性值""新增属性"只是还原过程的"附带效果",而非其设计初衷,具体拆解如下:
一、反序列化的本质功能:还原对象状态
序列化( serialize() )的作用是将内存中对象的状态(属性名+属性值)转化为结构化字符串(便于存储、传输);
反序列化( unserialize() )的对应功能是将这个结构化字符串,还原为内存中可操作的对象实例------它的设计目标是"精准复现对象原本的状态",而非"主动修改对象"。
简单说:序列化是"给对象拍快照",反序列化是"按快照恢复对象","恢复"才是核心,"修改"只是恢复时的可选操作(比如快照本身是"修改后"的状态)。
二、"修改"效果的本质:用"自定义快照"替代"默认状态"
你感受到的"修改",本质是攻击者构造了"篡改后的快照"(恶意序列化字符串),反序列化时按这个快照恢复对象,表现为"修改":
-
属性值覆盖:快照里的属性值(如 isVip=true )替代了类的默认值( isVip=false ),看似"修改",实则是恢复了快照中的状态;
-
新增属性:快照里包含类定义中没有的属性,反序列化时为了完整恢复快照状态,才动态新增属性------这是PHP动态类型特性的体现,而非反序列化的核心功能。
三、关键区分:功能 vs 利用场景
反序列化的核心功能 常见利用场景(表现为"修改")
还原对象的属性状态 构造恶意快照,覆盖关键属性值(如 isVip=true )
重建可操作的对象实例 新增动态属性,辅助触发类中已有代码
总结:"修改"是攻击者利用反序列化"还原状态"的功能,构造恶意快照后的"利用结果",而非反序列化本身的核心功能------它的本质是"恢复",而非"修改"。
需要我帮你梳理一份「反序列化核心功能 vs 常见利用场景」的对比表,明确两者的边界和关联吗?

这就是覆盖。
由石油改为公有是可以的。公有改成私友不知道。
这个也可以添加元素。

不是的,PHP序列化( serialize() )并非只能显示属性,序列化字符串会包含类的基础信息、属性数量与属性值,而反序列化后的对象还能调用类中的方法------你看到的输出只有属性相关内容,是因为序列化的核心是保存对象的状态(属性值),不会存储类的方法代码,具体拆解如下:
一、序列化字符串的组成(不只是属性)
从你截图的输出能看到,序列化字符串包含三类关键信息,而非仅属性:
-
类的基础标识: O:4:"user" 表示这是 user 类的对象( O 是Object标识, 4 是类名长度, user 是类名);
-
属性数量:第一行 :3: 表示对象有3个属性,第二行 :4: 表示有4个属性(含动态新增的属性);
-
属性详情:每个属性的"类型+长度+名称+值"(如 s:10:"username" 是私有属性名, s:6:"xiaodi" 是属性值)。
二、为什么看不到方法?方法不会被序列化
你定义的 ccc() 方法属于类的静态逻辑,而非对象的"状态":
-
序列化的设计目标是"保存对象的属性值"(比如 name=xiaodi 、 age=31 ),而非保存方法的代码;
-
方法是类定义时就固定的,反序列化后的对象会自动关联类的方法,所以你能调用 $demo->ccc() ,但序列化字符串里不会出现方法的任何内容。
三、反序列化后的对象能做的远不止"显示属性"
以你截图的 user 类为例,反序列化后的对象可以:
-
访问/修改属性:包括类定义的属性( name / sex / $age )和动态新增的属性(如截图里的 ddd );
-
调用类的方法:比如执行 demo-\>ccc() ,能正常运行方法内的逻辑( a=1; return 0; );
-
动态新增属性/方法:PHP是弱类型语言,还能给反序列化后的对象新增属性(如 $demo->newAttr = 'test' )或临时绑定方法。
四、验证:调用反序列化对象的方法(代码示例)
在你现有代码基础上添加一行,就能看到方法可正常调用:
php
<?php
class user{
private $name='xiaodi';
public $sex='man';
public $age=31;
public function ccc(){
$a=1;
return $a; // 修改返回值方便查看
}
}
$demo = new user();
s = serialize(demo);
u = unserialize(s);
// 调用方法并输出结果
echo "调用ccc()方法的返回值:" . $u->ccc(); // 输出1,证明方法可执行
?>
需要我帮你写一份序列化后调用方法+动态新增属性的完整测试代码,让你直观看到序列化不止能处理属性吗?
方法不能被序列化。
游戏内置的方法它会自动执行
不是没用,你框起来的 __construct() (构造方法)在实例化 ctfShowUser 类时会自动执行,直接改变了 $class 属性的类型和值,进而影响序列化的结果,是后续代码执行的关键铺垫,具体作用如下:
一、构造方法的核心作用
- 覆盖原有属性值
类中原本定义 public class = 'backDoor'; ( class 是字符串类型),而 __construct() 执行后, this-\>class = new backDoor(); 会把 class 属性从字符串改成 backDoor 类的对象实例------这是本质变化,序列化时保存的将是 backDoor 对象,而非原字符串。
- 为代码执行埋下入口
backDoor 类中定义了 code = "system('tac flag.php');" (执行系统命令的代码),当 class 变成 backDoor 对象后,后续只要触发相关魔术方法(如 __destruct() / __call() ),就能调用 $code 中的命令,实现代码执行。
二、验证:构造方法执行前后的属性差异
在代码中添加输出就能看到变化:
php
<?php
class ctfShowUser{
public $class = 'backDoor';
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
public $code="system('tac flag.php');";
}
// 实例化时自动执行构造方法
$obj = new ctfShowUser();
var_dump($obj->class); // 输出:object(backDoor)#2 (1) { ["code"]=> string(20) "system('tac flag.php');" }
// 对比:如果注释构造方法,$class是字符串"backDoor"
?>
三、对序列化的影响
执行 a = serialize(new ctfShowUser()); 后,序列化字符串中 class 属性对应的是 backDoor 对象的序列化内容,而非原字符串 'backDoor' ------这是后续反序列化利用代码执行的核心前提,没有这个构造方法, $class 只是普通字符串,无法触发命令执行逻辑。
简单说,这个构造方法是**把"静态字符串"变成"可执行的对象实例"**的关键,绝非无用代码。
需要我帮你补全代码,添加触发命令执行的魔术方法,让你直观看到这个构造方法如何配合实现代码执行吗?
这个我自己实验的时候倒是提前知道了。





后面的都不会解析成字符串。
我懂他的意思了,但一开始会导致我们解析失败(如果不匹配的话从5→6会多解析导致失败,如果匹配的话,后面的东西就释放出来了。

你*的我都没注意到后面还有。

黑客直接把282位给挤占了。
逃走的那一部分就是执行的部分,后面还是得要按部就班。
后面才是攻击的东西吧,前面是自带的东西。

第1个看第1位拼接(因为额度执行完了,所以后面直接执行),第2个看第2位(固定值吞吃),他拼接执行的东西一个是1一个是2,有个东西是自带的。
这方法太奇葩了,直接可以修改属性。

这个感觉是原生类种调用这个魔术方法的方法。

可以创造已经有的类
反序列化既不能创造类/方法,也不是只能在非类的代码中使用;能否调用类方法,核心取决于目标环境中是否存在该类,以及是否满足方法的触发条件,和代码是否为类结构无关。
一、核心结论:反序列化"不能创造,只能触发已有"
- 无法创造类/方法
反序列化的唯一作用是将序列化字符串还原为对象实例,这个过程不会生成新的类,也不会新增/修改类的方法。所有被调用的方法,都是类提前定义好的(要么是PHP原生内置类的自带方法,要么是目标代码中自定义类的方法)。
-
比如调用 Exception 类的 __toString ,是因为PHP环境默认自带这个类和方法;
-
比如调用自定义 Test 类的 __destruct ,是因为目标服务器的代码里已经定义了 Test 类和该方法。
若目标环境中没有对应的类,反序列化只会生成 __PHP_Incomplete_Class 对象,无法调用任何方法。
- 调用方法的前提是"触发条件"
反序列化本身不会主动调用方法,而是还原对象后,满足了类方法的触发规则,才会执行方法:
-
魔法方法( __wakeup / __destruct / __toString )有固定触发规则(如对象销毁触发 __destruct 、对象转字符串触发 __toString );
-
普通方法需要业务逻辑主动调用(如 $obj->func() )。
这些触发规则和"代码是否是类结构"无关,只和对象的使用方式有关。
二、"不是只能在非类的代码中使用"
反序列化的执行和触发,不受代码是否为类结构的限制,只要满足条件,在类的方法里、全局脚本里、函数里都能触发类方法:
- 在全局脚本中触发
php
// 全局代码(非类)
class Test {
public function __destruct() {echo "触发方法";}
}
unserialize('O:4:"Test":0:{}'); // 反序列化后对象销毁,触发__destruct
- 在类的方法中触发
php
class Demo {
public function run() {
// 在类的方法里执行反序列化
unserialize('O:4:"Test":0:{}');
}
}
class Test {
public function __destruct() {echo "在类方法中触发";}
}
$demo = new Demo();
$demo->run(); // 同样触发Test类的__destruct
- 在函数中触发
php
function testFunc() {
unserialize('O:4:"Test":0:{}');
}
class Test {
public function __destruct() {echo "在函数中触发";}
}
testFunc(); // 依然触发__destruct
简单说:反序列化调用类方法的关键,是目标有这个类+满足方法触发条件,和代码是否是类结构没有关系。
需要我帮你写一个不同代码场景下反序列化触发类方法的测试代码,让你直观看到全局、类方法、函数中的触发效果吗?


框架反序列化的核心流程是 "找到入口→构造链路→触发执行→达成攻击",全程依赖框架的类机制、PHP反序列化规则和漏洞环境,以下是结合ThinkPHP/laravel等主流框架的详细分步流程(从攻击方视角拆解,含技术细节和框架特性):
一、前置准备:明确攻击目标与环境探查
在开始流程前,需先掌握目标框架的关键信息,这是后续构造利用链的基础:
-
确认框架类型与版本:通过网站响应头(如 X-Powered-By: ThinkPHP/5.1.41 )、报错页面、robots.txt等,确定框架是ThinkPHP、Laravel、Yii等,以及具体版本(不同版本的可利用类和漏洞点差异极大);
-
探查PHP环境配置:通过探针、报错信息或盲测,了解PHP版本(如PHP 7.4支持更多原生类)、 disable_functions (是否禁用 system / eval 等危险函数)、 allow_url_include 等关键配置(影响后续攻击方式);
-
收集框架核心类信息:下载对应版本的框架源码,梳理核心类的继承关系、魔术方法( __destruct / __wakeup / __toString )、危险方法(含 eval / file_put_contents / system 的方法)------框架的"自动加载机制"会帮攻击者自动加载这些类,无需手动引入。
二、第一步:寻找反序列化点(触发入口)
这是流程的"敲门砖",没有反序列化点,后续所有操作都无法触发。核心目标是找到 "用户可控参数 + 直接传入 unserialize() " 的调用位置:
- 反序列化点的3种常见来源:
- 「开发者不安全编码」:最常见,开发者为实现数据传输/存储,直接将用户输入反序列化,如:
php
// ThinkPHP控制器中示例(开发者写的反序列化点)
public function test() {
data = _GET['payload']; // 用户可控参数
unserialize($data); // 直接反序列化,无过滤
}
-
「框架历史漏洞」:框架自身的参数处理逻辑存在缺陷,隐式调用 unserialize() ,如:
-
ThinkPHP 5.0.x的 cookie 反序列化漏洞:框架默认对cookie值进行反序列化,且cookie参数用户可控;
-
Laravel 5.4.x的 session 反序列化漏洞:session存储的序列化字符串被未过滤反序列化。
-
「第三方组件漏洞」:网站使用的插件/库(如支付组件、缓存组件)中存在反序列化点,如Phar反序列化(利用 file_exists() 等函数触发Phar文件的反序列化)。
- 验证反序列化点有效性:
构造简单的序列化字符串(如包含 echo 的测试类),传入疑似反序列化点,若能触发预期输出(如弹窗、日志记录),则确认该点可利用。
三、第二步:分析框架可利用类与构造POP链
这是流程的核心,目标是找到框架中 "魔术方法 + 危险函数"的调用链路(即POP链),框架的价值在于提供了大量"现成的类和方法",无需攻击者自建:
- 筛选框架中的"关键类":
从框架源码中筛选满足以下条件的类(重点看 think / Illuminate 等核心命名空间下的类):
-
包含触发型魔术方法( __destruct / __wakeup / __toString / __call );
-
魔术方法中调用了其他类的方法(可衔接链路);
-
最终链路中存在危险函数( eval / system / file_put_contents / exec 等)。
框架典型可利用类示例(ThinkPHP):
-
think\template\driver\Php : __destruct() → display() → 调用 eval() 执行PHP代码;
-
think\model\Pivot : __toString() → toArray() → 调用 array_merge() (可触发异常或数据读取);
-
think\config\Config : set() 方法可写入配置,结合其他方法触发配置生效。
- 构造POP链的逻辑的链路:
POP链的核心是"链式调用",即: 反序列化触发魔术方法 → 调用框架中间方法 → 触发危险函数 ,以ThinkPHP执行系统命令为例,完整POP链如下:
plaintext
- 反序列化触发类A的__destruct() → 2. __destruct()调用类B的display()方法 → 3. display()调用eval() → 4. eval()执行system('whoami')
具体代码逻辑拆解:
- 类A(攻击者构造的触发类):
php
class Trigger {
private $template;
public function __construct() {
// 实例化框架的危险类Php
$this->template = new think\template\driver\Php();
// 给Php类的template属性赋值(恶意代码)
$this->template->template = '<?php system("whoami"); ?>';
}
// 反序列化后脚本结束时触发__destruct()
public function __destruct() {
// 调用Php类的display()方法,衔接链路
$this->template->display();
}
}
- 框架类 think\template\driver\Php 的核心代码(自带逻辑):
php
class Php {
public $template; // 存储模板内容(攻击者可控)
public function display() {
// 危险函数:直接执行template属性的代码
eval('?>'.$this->template);
}
}
- 利用框架自动加载机制:
构造POP链时,无需手动引入框架类(如 think\template\driver\Php ),因为框架的自动加载器(如ThinkPHP的 Loader::autoload() )会根据命名空间自动从磁盘加载对应的类文件------这是框架反序列化的关键便利之处,原生PHP反序列化需手动引入类。
四、第三步:处理序列化字符串的格式与转义
框架的参数处理逻辑(如过滤、转义)会破坏序列化字符串结构,需针对性处理,否则反序列化会失败:
- 确保序列化格式合规:
严格遵循PHP序列化格式(如对象格式 O:类名长度:"类名":属性个数:{属性内容}; ),重点检查:
-
类名的命名空间转义:框架类的命名空间含 \ ,需转义为 \\ (若通过URL传参,需再次转义为 \\\\ ),如 think\\template\\driver\\Php ;
-
属性长度匹配:字符串属性的长度标识必须与实际内容一致(如 s:18:"<?php system('whoami'); ?>"; );
-
结构符号完整: {} / ; / : 等分隔符不能缺失,否则框架解析时会截断。
- 处理框架的输入过滤:
目标框架可能对用户输入做过滤(如 addslashes() 转义 " / ' / \ ),需针对性规避:
-
若框架用 addslashes() ,序列化字符串中的 " 需转义为 \" ,或用单引号包裹属性值;
-
若框架限制参数长度,需精简POP链(如减少不必要的属性),确保序列化字符串长度不超限。
- 生成最终payload:
实例化构造好的触发类,调用 serialize() 生成序列化字符串,示例:
php
$trigger = new Trigger();
payload = serialize(trigger);
// 若需URL传参,需URLEncode(处理特殊字符)
payload = urlencode(payload);
五、第四步:发送payload触发反序列化
将处理后的payload传入第一步找到的反序列化点,触发 unserialize() 执行:
- 选择传参方式:
根据反序列化点的参数类型(GET/POST/Cookie/Header)选择传参方式:
-
若反序列化点是 unserialize($_GET['payload']) :直接拼接URL,如 http://target.com/test.php?payload=O:7:"Trigger":1:{s:10:"%00Trigger%00template";O:27:"think\template\driver\Php":1:{s:8:"template";s:26:"<?php system('whoami'); ?>";}} ;
-
若反序列化点是Cookie(如ThinkPHP的cookie反序列化漏洞):将payload写入Cookie的对应字段(如 thinkphp_show_page_trace=payload )。
- 规避框架的安全防护:
部分框架会对反序列化做防护(如ThinkPHP的 deny_func_list 禁用危险函数),需针对性绕过:
-
替换危险函数:用 exec() / shell_exec() 替代被禁用的 system() ;
-
利用框架自身函数:用框架的内置方法(如 think\helper\Arr::get() )间接调用危险函数,绕过黑名单。
六、第五步:反序列化执行与恶意逻辑触发
当框架执行 unserialize($payload) 后,按以下流程触发恶意代码:
- PHP解析序列化字符串:
PHP按格式解析payload,找到 Trigger 类,框架自动加载该类(若为自定义触发类,需确保已在目标环境中存在------或通过框架漏洞引入);
- 实例化对象并赋值属性:
PHP创建 Trigger 类的对象,将payload中的属性值(如 $template 对象)赋值给该对象;
- 触发魔术方法:
-
若POP链用 __wakeup() :反序列化时立即触发;
-
若用 __destruct() :脚本执行结束时(所有代码执行完后)触发;
-
若用 __toString() :当对象被当作字符串使用时(如 echo $obj )触发。
- 链式调用框架方法:
魔术方法调用框架类的中间方法(如 Trigger::__destruct() 调用 Php::display() ),框架的核心逻辑按正常流程执行;
- 执行恶意代码:
最终调用到危险函数(如 eval() ),执行 system('whoami') 等恶意逻辑,达成攻击目标(命令执行、文件读取、权限提升等)。
七、流程收尾:处理执行结果与清理痕迹
- 获取执行结果:
若恶意逻辑是命令执行,结果可能直接输出到页面(如 whoami 的结果),或需通过文件写入(如 file_put_contents('result.txt', shell_exec('ls')) )后读取;
- 清理痕迹:
若需长期控制,需删除日志(如框架的 runtime/log 目录下的访问日志)、清理临时文件(如 result.txt ),避免被管理员发现。
框架反序列化与原生PHP反序列化的核心差异
环节 原生PHP反序列化 框架反序列化
类来源 需手动引入或已定义的类 框架自带大量可利用类(自动加载)
POP链构造 依赖少量原生类,链路短 框架类方法丰富,可构造复杂长链路
安全防护 无内置防护,仅依赖PHP配置 框架可能有黑名单、参数过滤等防护
反序列化点来源 多为开发者手动调用 开发者代码 + 框架历史漏洞 + 第三方组件
关键总结
框架反序列化的核心是 "借框架的'壳'(类和方法),走自己的'路'(POP链)":框架提供了自动加载、丰富的类方法和链路,攻击者只需找到反序列化点,构造符合框架逻辑的POP链,处理好格式和转义,就能触发恶意代码执行。整个流程的关键是 "适配框架环境"------不同框架的可利用类、防护机制不同,需针对性调整POP链和payload。
我可以帮你基于ThinkPHP 5.1.x版本,写一份 完整的框架反序列化实战演示代码(含反序列化点搭建、POP链构造、payload生成和命令执行测试),需要吗?