一、 核心原理
1. Phar 文件的“四段式”结构
Phar (Php ARchive) 类似 Java 的 JAR 包,其文件结构由四部分组成:
| 结构 | 作用 | 攻击面 |
|---|---|---|
| Stub | 文件头标识,格式必须为 xxx<?php xxx; __HALT_COMPILER();?> |
伪造文件头(如 GIF89a)绕过检测 |
| Manifest | 存储压缩文件属性、权限及 Meta-data | 核心攻击点:Meta-data 以序列化形式存储 |
| Contents | 压缩文件的实际内容 | 一般不涉及 |
| Signature | 文件末尾的签名(MD5/SHA1/SHA256/SHA512) | 修改 Meta-data 后必须重算签名 |
注:签证尾部的01代表MD5加密,02代表SHA1加密,04代表SHA256加密,08代表SHA512加密
2. 源码级触发机制
Phar 之所以能反序列化,是因为 PHP 底层在解析 Phar 文件的 Manifest 时,会调用 phar_parse_metadata(),该函数内部直接调用了 php_var_unserialize()。
完整调用链:
1 | file_get_contents("phar://xxx.phar/test.txt") |
3. 可触发 Phar 的函数清单
只要函数底层调用了 php_stream_open 或 stat 系列函数,且参数可控,即可触发:
- 读取类:
file_get_contents,fopen,readfile,fgets - 信息类:
file_exists,is_file,is_dir,filesize,stat,filemtime - 写入类:
file_put_contents,copy,move_uploaded_file - 其他:
getimagesize,md5_file,sha1_file
二、Phar 的生成、修改与绕过体系
1. 基础生成模板
1 |
|
(注:生成时需确保 php.ini 中 phar.readonly = Off)
2. WAF 绕过体系化总结
当 WAF 或代码对上传和读取进行限制时,我们需要以下“武器”进行绕过:
| 限制类型 | 绕过姿势 | 原理说明 |
|---|---|---|
| 后缀检测 | 改后缀为 .jpg/.png/.txt |
PHP 识别 Phar 靠 Stub,不靠后缀 |
| 文件头检测 | 修改 Stub:GIF89a<?php __HALT... |
伪造为图片文件头 |
__HALT_COMPILER 关键字检测 |
Gzip / Bzip2 压缩 | 压缩后关键字消失,利用 phar://shell.gz/test.txt 解析 |
phar:// 协议过滤 |
协议嵌套 | compress.zlib://phar://shell.phar/test.txt |
__wakeup 属性覆盖 |
修改序列化属性个数 | 用 010 Editor 将 O:1:"A":1:{ 改为 O:1:"A":2:{ (CVE-2016-7124) |
| 修改文件后签名失效 | Python 重算签名 | 提取正文,计算 SHA1/SHA256,拼接 GBMB 标识 |
三、 实战演练
例题一:Unserialize Again (基础 Phar + 签名修复)
考点:
php://input写入、__wakeup绕过、SHA256 签名修复
1. 漏洞链路分析
题目提供了 file_put_contents($pear, urldecode(php://input)) 用于写入文件,以及 file_exists($Adam) 用于触发。
1 | POST body (php://input) -> 写入 hacker.phar |
2. 核心难点:绕过 __wakeup 与签名修复
story 类的 __wakeup 中有 die(),必须修改属性个数绕过。但修改 010 Editor 中的字节后,Phar 签名会损坏。
通过 010 Editor 观察文件末尾 00 前的 03,得知本题使用 SHA256 签名(32字节)。
修复脚本(实战利器):
1 | from hashlib import sha256 |
3. 最终 Exploit
由于浏览器直接上传二进制 phar 容易损坏,使用 Python 发送请求并进行 URL 编码:
1 | import requests, urllib.parse |
例题二:Ez to getflag (Zip 压缩 + Session 条件竞争)
考点:文件内容过滤绕过、Zip 压缩 Phar、Session Upload Progress 条件竞争
1. 漏洞链路分析 (POP Chain)
本题的难点在于 POP 链的构造和文件包含的利用:
1 | Test::__destruct() -> echo $this->str (str=Upload) |
2. 核心难点:绕过内容过滤与文件包含
- 绕过上传过滤:代码使用
preg_match过滤了<?php、system等关键字。我们将生成的shell.phar使用 Zip 格式压缩,并改名为shell.png上传。Zip 压缩可以完美破坏 Phar 的文本特征,且phar://协议原生支持解析 Zip 格式的 Phar。 - 实现 RCE (条件竞争):
include包含的是 Session 临时文件。我们利用 PHP 的 Session Upload Progress 机制,在上传文件时通过 POST 传入PHP_SESSION_UPLOAD_PROGRESS字段,将恶意代码写入/tmp/sess_dre0。由于 PHP 会在请求结束后清理 Session 文件,必须使用多线程条件竞争。
3. 竞争脚本核心逻辑
1 | # 线程1:疯狂上传文件,利用 Session 机制写入临时文件 /tmp/sess_dre0 |
例题三:君の名は (纯反序列化进阶:C: 协议与反射 RCE)
考点:正则绕过 (
O:->C:)、__unserialize、ReflectionFunctionRCE
(注:本题虽非 Phar,但作为反序列化专题的进阶补充,极具价值)
1. 漏洞链路分析
1 | 正则过滤 /^[Oa]:[\d]+/i -> 使用 C:11:"ArrayObject" 绕过 |
2. 核心难点:C: 协议与 Lambda 函数
C:协议:当正则封死了O:(对象) 和a:(数组) 时,我们可以使用C:(Custom Object Serialization)。它会调用类的__unserialize()方法。我们借用 PHP 内置类ArrayObject来包装我们的恶意 payload。create_function与 反射:题目开头有create_function("", 'die(/readflag);');。create_function底层会创建一个名为\0lambda_1的匿名函数。我们通过ReflectionFunction(反射类) 实例化该函数,并调用其invoke()方法执行代码。
3. Payload 构造细节
1 | $a = new Taki(); $b = new Mitsuha(); $c = new KatawareDoki(); |
避坑指南:create_function 每次访问页面都会重新生成,lambda 后的数字会递增(如 \0lambda_2),实战中需重开容器或编写脚本动态爆破数字。
四、 总结:POP 链终点速查表
在构造反序列化链时,找到合适的“终点”(Sink)至关重要。以下是常见的 RCE 终点总结:
| 终点类型 | 适用场景 | 示例代码 / 函数 |
|---|---|---|
| 命令执行 | 参数可控且无过滤 | system(), exec(), passthru(), shell_exec() |
| 代码执行 | 需要执行 PHP 代码 | eval(), assert(), preg_replace (e修饰符), create_function |
| 反射调用 | 无直接执行函数时 | ReflectionFunction->invoke(), ReflectionMethod->invoke() |
| 文件包含 | 可控制文件路径时 | include(), require(), SplFileObject |
| 文件写入 | 配合 .htaccess 或日志 |
file_put_contents(), fwrite() |
五、参考文章
phar反序列化+文件上传与例题讲解_phar文件上传-CSDN博客
php phar反序列化总结 - FreeBuf网络安全行业门户
[PHP Phar反序列化学习 - Zh1z3ven - 博客园](https://www.cnblogs.com/CoLo/p/16786627.html#:~:text=Phar反序列化 Phar之所以能反序列化,是因为Phar文件会以序列化的形式存储用户自定义的 meta-data%2CPHP使用,phar_parse_metadata 在解析meta数据时,会调用 php_var_unserialize 进行反序列化操作。)