网安学习第24天 PHP安全——PHP反序列化
一、序列化与反序列化
1、序列化serialize()
序列化是什么?序列化就是把程序中的对象、数组、结构体等复杂数据,转换成可以存储或传输的格式。
简单说:
把“内存里的对象”变成“字符串/字节流”。
例如 PHP 中有一个对象:
$user = [
"name" => "admin",
"age" => 18
];
序列化后可能变成:
a:2:{s:4:"name";s:5:"admin";s:3:"age";i:18;}
其中各个字段代表的含义:

常见序列化格式包括:
JSON
XML
YAML
PHP serialize
Java Serializable
Python pickle
2、反序列化unserialize()
序列化是什么?反序列化就是把序列化后的字符串/字节流,还原成程序中的对象或数据结构。
简单说:
把“字符串/字节流”重新变回“内存里的对象”。
例如:
$data = 'a:2:{s:4:"name";s:5:"admin";s:3:"age";i:18;}';
$user = unserialize($data);
反序列化后,$user 又变成了数组:
[
"name" => "admin",
"age" => 18
]
所以序列化与反序列化 的操作可以直观地看作为:
3\
3、为什么反序列化会有漏洞?
问题核心在于:
如果程序反序列化了用户可控的数据,就可能被攻击者构造恶意对象。
危险点不是“反序列化”本身,而是:
反序列化的数据来自用户输入
+
程序中存在危险的魔术方法/特殊方法
+
这些方法里有文件操作、命令执行、SQL操作等危险逻辑
就可能形成反序列化漏洞。
4. PHP 反序列化漏洞例子
例如 PHP 中有一个类:
<?php
class Test {
public $cmd;
function __destruct() {
system($this->cmd);
}
}
?>
这里的 __destruct() 是魔术方法,对象销毁时会自动执行。
如果程序这样写:
$data = $_GET['data'];
unserialize($data);
那么攻击者可以构造一个恶意序列化字符串:
O:4:"Test":1:{s:3:"cmd";s:2:"id";}
反序列化后,程序会创建 Test 对象,并且 $cmd = "id"。
脚本结束时触发:
__destruct()
于是执行:
system("id");
这就可能造成 RCE,远程命令执行。
二、常见的魔术方法
__construct(): //当对象new的时候会自动调用
__destruct()://当对象被销毁时会被自动调用
__sleep(): //serialize()执行时被自动调用
__wakeup(): //unserialize()时会被自动调用
__invoke(): //当尝试以调用函数的方法调用一个对象时会被自动调用
__toString(): //把类当作字符串使用时触发
__call(): //调用某个方法,若方法存在,则调用;若不存在,则会去调用__call函数。
__callStatic(): //在静态上下文中调用不可访问的方法时触发
__get(): //读取对象属性时,若存在,则返回属性值;若不存在,则会调用__get函数
__set(): //设置对象的属性时,若属性存在,则赋值;若不存在,则调用__set函数。
__isset(): //在不可访问的属性上调用isset()或empty()触发
__unset(): //在不可访问的属性上使用unset()时触发
__set_state(),调用var_export()导出类时,此静态方法会被调用
__clone(),当对象复制完成时调用
__autoload(),尝试加载未定义的类
__debugInfo(),打印所需调试信息

三、反序列化漏洞利用
1、POP链
POP 链就是 PHP 反序列化漏洞里的“调用链”。全称:Property-Oriented Programming Chain(面向属性编程链)。
它的核心是:
攻击者通过控制反序列化对象的属性,让程序自动触发魔术方法,然后一步步调用已有代码,最后到达危险函数。
POP常用于上层语言构造特定调用链的方法,序列化攻击都在PHP魔术方法中出现可利用的漏洞,因自动调用触发漏洞,但如关键代码没在魔术方法中,而是在一个类的普通方法中。这时候就可以通过构造POP链寻找相同的函数名将类的属性和敏感函数的属性联系起来。
2. POP 链的基本结构
一条 POP 链一般有三部分:
入口点 Trigger
↓
中间跳板 Gadget
↓
危险终点 Sink
入口点 Trigger
一般是魔术方法,例如:
__destruct()
__wakeup()
__toString()
中间跳板 Gadget
就是普通业务代码中的方法,比如:
$this->obj->run();
$this->handler->save();
$this->logger->write();
这些方法本来可能不是漏洞,但可以被攻击者利用来继续调用下一个对象。
危险终点 Sink
最终危险操作,例如:
system()
eval()
assert()
include()
require()
file_put_contents()
unlink()

3. 一个简单例子
看这三个类:
class A {
public $b;
public function __destruct() {
$this->b->run();
}
}
class B {
public $c;
public function run() {
$this->c->write();
}
}
class C {
public $filename;
public $content;
public function write() {
file_put_contents($this->filename, $this->content);
}
}
单独看:
A::__destruct()
B::run()
C::write()
都像正常代码。
但是如果程序中有:
unserialize($_COOKIE['user']);
攻击者就可能构造对象关系:
A 对象
└── b = B 对象
└── c = C 对象
├── filename = 可控文件名
└── content = 可控内容
执行流程变成:
unserialize()
↓
恢复 A 对象
↓
脚本结束触发 A::__destruct()
↓
调用 $this->b->run()
↓
进入 B::run()
↓
调用 $this->c->write()
↓
进入 C::write()
↓
file_put_contents($filename, $content)
这就是一条 POP 链。
三、属性类型
1、对象变量属性:
public(公共的):在本类内部、外部类、子类都可以访问
protect(受保护的):只有本类或子类或父类中可以访问
private(私人的):只有本类内部可以使用
2、序列化数据显示:
public属性序列化的时候格式是正常成员名
private属性序列化的时候格式是%00类名%00成员名
protect属性序列化的时候格式是%00*%00成员名
3、例
<?php
header("Content-type: text/html; charset=utf-8");
//public private protected说明
class test{
public $name="xiaodi";
private $age="31";
protected $sex="man";
}
$a=new test();
$a=serialize($a);
print_r($a);
?>
输出为:
O:4:"test":3:{s:4:"name";s:6:"xiaodi";s:9:" test age";s:2:"31";s:6:" * sex";s:3:"man";}
其中:
age ——> " test age"
sex ——> " * sex"
四、漏洞绕过
1、__wakeup绕过
为什么要绕过 __wakeup()?因为有些反序列化利用链中,__wakeup() 会破坏攻击者控制的属性。
比如:
class Test {
public $cmd;
public function __wakeup() {
$this->cmd = "echo safe";
}
public function __destruct() {
system($this->cmd);
}
}
攻击者本来想控制:
$cmd = "id";
但是反序列化时会先触发:
__wakeup()
把 $cmd 改成:
echo safe
这样 POP 链就被破坏了。
所以绕过 __wakeup() 的目的就是:
让对象恢复出来
但是不执行 __wakeup()
保留攻击者控制的属性
等到 __destruct() 或其他方法触发
当属性数量不一致时会绕过__wakeup()
正常序列化对象:
O:4:"Test":1:{s:3:"cmd";s:2:"id";}
这里属性数量是:1。因为只有一个属性:$cmd
如果把属性数量故意改大:
O:4:"Test":2:{s:3:"cmd";s:2:"id";}
注意这里:属性数量从 1 改成了 2。但实际后面只有一个属性。
在部分老版本 PHP 中,这种属性数量不一致会导致:
对象仍然可能被反序列化
但是 __wakeup() 不会被调用
于是就达到了绕过效果。
2、字符逃逸
字符逃逸在 PHP 反序列化里,核心意思是:
原本应该被当成“普通字符串内容”的字符,因为长度变化或过滤处理,逃出了字符串边界,被
unserialize()当成了新的序列化结构来解析。
它经常出现在:
serialize()
↓
字符串过滤 / 替换
↓
unserialize()
这个流程里。
参考:php反序列化-字符逃逸看这一篇就够了_php 反序列化-CSDN博客
五、反序列化链项目
1、-NotSoSecure
https://github.com/NotSoSecure/SerializedPayloadGenerator
为了利用反序列化漏洞,需要设置不同的工具,如 YSoSerial(Java)、YSoSerial.NET、PHPGGC 和它的先决条件。DeserializationHelper 是包含对 YSoSerial(Java)、YSoSerial.Net、PHPGGC 和其他工具的支持的Web界面。使用Web界面,您可以为各种框架生成反序列化payload.
Java – YSoSerial
NET – YSoSerial.NET
PHP – PHPGGC
Python - 原生
2、-PHPGGC
https://github.com/ambionics/phpggc
PHPGGC是一个包含unserialize()有效载荷的库以及一个从命令行或以编程方式生成它们的工具。当在您没有代码的网站上遇到反序列化时,或者只是在尝试构建漏洞时,此工具允许您生成有效负载,而无需执行查找小工具并将它们组合的繁琐步骤。 它可以看作是frohoff的ysoserial的等价物,但是对于PHP。目前该工具支持的小工具链包括:CodeIgniter4、Doctrine、Drupal7、Guzzle、Laravel、Magento、Monolog、Phalcon、Podio、ThinkPHP、Slim、SwiftMailer、Symfony、Wordpress、Yii和ZendFramework等。
七、反序列化框架利用-ThinkPHP&Yii&Laravel
通过上述工具,构造payload实现反序列化
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)