漏洞速览:3亿下载量的Excel库出事了
2026年7月28日,PHP生态中最核心的电子表格处理库 PhpSpreadsheet 连续修复了三个安全漏洞,其中最严重的 CVE-2026-59931(CVSS 7.7)允许攻击者通过上传一个精心构造的 XLSX 文件,绕过域名白名单限制,从云服务器内部窃取 AWS、阿里云等云平台的临时访问凭证。这个库在 Packagist 上的累计下载量超过 3.26亿次,日均下载33万次,几乎所有用 PHP 开发的后台管理系统、ERP、CRM、财务报表导出功能都依赖它。
另外两个漏洞 CVE-2026-59932 和 CVE-2026-59933(均 CVSS 7.5)则利用同样一个恶意文件就能让 PHP 进程内存耗尽崩溃,甚至不需要应用真正解析文件内容——仅仅在自动文件类型检测阶段就会触发。如果你的系统允许用户上传 Excel 文件,这组漏洞你应该立即关注。
2分钟快速自查
第一步:确认你的项目是否依赖了 PhpSpreadsheet。在项目根目录执行以下命令查看锁文件中的版本:
composer show phpoffice/phpspreadsheet | grep -i version
# 或者直接查看 composer.lock
grep -A2 '"phpoffice/phpspreadsheet"' composer.lock | grep version
第二步:确认代码中是否使用了受影响的功能。搜索以下关键函数调用:
# 搜索 SSRF 相关的 WEBSERVICE 公式和域名白名单
grep -rn "setDomainWhiteList\|getCalculatedValue\|WEBSERVICE" src/
# 搜索文件上传相关的读取路径(DoS 漏洞相关)
grep -rn "Reader\\\\Xls\|Reader\\\\Gnumeric\|canRead\|->load(" src/
第三步:判断版本是否在受影响范围。对照下表检查:
结论判断:如果你的版本低于以下修复版本,则受影响——5.x 系列需升级到 5.8.1,3.x 系列需升级到 3.10.7,2.x 系列需升级到 2.4.7 或 2.1.18,1.x 系列需升级到 1.30.6。如果你同时满足「允许用户上传 Excel 文件」这个条件,风险等级为高危,建议立即升级。
影响范围:谁会中招
要理解这个漏洞的影响范围,首先需要搞清楚三个 CVE 各自的触发条件不同。
| CVE编号 | 类型 | 触发条件 | CVSS |
| CVE-2026-59931 | SSRF(服务端请求伪造) | 使用 setDomainWhiteList() + getCalculatedValue() + 允许用户上传XLSX | 7.7 |
| CVE-2026-59932 | 拒绝服务(Gzip炸弹) | 允许用户上传 .gnumeric 文件,触发 Gnumeric 读取 | 7.5 |
| CVE-2026-59933 | 拒绝服务(死循环) | 允许用户上传 .xls/OLE 文件,触发 canRead() 自动检测 | 7.5 |
值得特别注意的是,CVE-2026-59931 的攻击面比另外两个更窄,但危害也更大。它要求应用同时满足三个条件:第一,调用了 setDomainWhiteList() 方法设置域名白名单;第二,对用户上传的文件调用了 getCalculatedValue() 来计算公式结果;第三,允许用户上传 XLSX 文件。只有同时满足这三个条件,攻击者才能利用 SSRF 窃取内网信息。而两个 DoS 漏洞的触发门槛低得多——只要应用接受任意格式的电子表格上传,甚至在自动检测文件类型的时候就会中招。
不受影响的情况:如果你只使用 PhpSpreadsheet 来生成导出 Excel 文件(单向写入,不读取用户上传的文件),则完全不受这三个漏洞影响。大部分国内后台管理系统的「导出报表」功能就属于这种情况。
常见误区一:用了域名白名单就安全了。这是最大的误区。CVE-2026-59931 恰恰攻击的就是域名白名单机制本身。白名单只验证了初始请求的域名,但 PHP 的 file_get_contents() 在收到 301/302 重定向时会自动跟随跳转(默认最多20跳),而跳转目标根本不会被重新验证。攻击者只需要找一个白名单域名上的开放重定向漏洞,或者自己控制一个白名单域名,就能把请求重定向到任意内网地址。
常见误区二:我不解析公式就不会中招。对于两个 DoS 漏洞来说这是错误的。CVE-2026-59933 的触发路径经过 Reader\Xls::canRead(),也就是自动文件类型检测函数。很多框架在接收文件上传后会调用 canRead() 判断文件类型,这个阶段就已经触发了死循环,还没等到你决定是否解析就已经崩溃了。
常见误区三:限制了上传文件大小就安全了。对于 CVE-2026-59932 的 Gzip 炸弹攻击,文件大小限制完全无效。攻击者上传的 .gnumeric 文件本身可能只有几十KB,但它的 gzip 压缩内容解压后可以膨胀到数百MB甚至GB级别。PhpSpreadsheet 的 Gnumeric 读取器在解压前没有检查解压后的数据大小,直接调用 gzdecode() 对全部压缩内容进行解压,瞬间撑爆 PHP 进程的内存限制。
技术分析:一个XLSX公式如何打穿内网
这三个漏洞中,技术含量最高、危害最严重的是 CVE-2026-59931。它的核心问题出在 Calculation/Web/Service.php 文件中的 webService() 方法。这个方法负责处理 Excel 的 WEBSERVICE() 公式函数——这是 Excel 2013 引入的一个函数,可以通过公式向外部 URL 发起 HTTP 请求并将响应内容作为单元格的值返回。
在 PhpSpreadsheet 5.4.0 版本之前,WEBSERVICE() 函数可以请求任意 URL,这本身就是一个 SSRF 漏洞。开发团队在 PR #4751 中引入了 setDomainWhiteList() 方法作为缓解措施,要求应用开发者主动设置允许访问的域名白名单。逻辑看起来没问题:先用 parse_url() 解析 URL 提取主机名,然后检查主机名是否在白名单中,通过了才调用 file_get_contents() 获取内容。
问题出在两个地方。第一,白名单检查只执行一次——只在初始 URL 上检查。第二,发起 HTTP 请求时使用的 PHP 流上下文(stream context)没有禁用重定向跟随。来看看漏洞代码的关键部分:
// 漏洞代码:Calculation/Web/Service.php
// 第一步:白名单检查(只检查初始URL的主机名)
$domainWhiteList = $cell->getParent()?->getDomainWhiteList() ?? [];
$host = parse_url($url, PHP_URL_HOST);
if (!in_array($host, $domainWhiteList, true)) {
return ExcelError::VALUE(); // 不在白名单,拒绝
}
// 第二步:发起HTTP请求(这里出问题了!)
$ctxArray = [
'http' => [
'user_agent' => 'Mozilla/5.0 ...',
// ⚠️ 没有设置 follow_location => 0
// ⚠️ 没有设置 max_redirects => 0
// PHP默认会跟随最多20次重定向!
],
];
$ctx = stream_context_create($ctxArray);
$output = @file_get_contents($url, false, $ctx);
// file_get_contents 遇到302会自动跳转
// 跳转后的目标URL完全不受白名单约束!
这就形成了一个完整的攻击链。攻击者制作一个包含 WEBSERVICE 公式的 XLSX 文件,公式中的 URL 指向一个白名单域名上的重定向端点。PhpSpreadsheet 在计算公式时,先验证了白名单域名——通过。然后调用 file_get_contents() 发起请求,白名单域名的服务器返回一个 302 重定向,把请求跳转到 http://169.254.169.254/latest/meta-data/——这是 AWS、阿里云 ECS 等云平台提供的元数据服务接口,PHP 自动跟随了这个重定向,获取了云服务器的临时访问凭证,然后将凭证内容作为公式的计算结果返回。攻击者只需要读取这个单元格的值,就拿到了你的云服务器密钥。
除了重定向绕过,白名单还有另一个缺陷:它使用 parse_url() 提取主机名但忽略了端口号。这意味着如果你将 example.com 加入白名单,那么 example.com:1 到 example.com:65535 的所有端口都会被放行,攻击者可以用这个特性对该域名的所有端口进行扫描探测。这是一个完整的读取型 SSRF——攻击者可以获取最多 32767 字节的 HTTP 响应体作为单元格计算结果,足以读取完整的云凭证、内部 API 响应、数据库连接字符串等敏感数据。
| CVSS 向量项 | 值 | 解读 |
| 攻击向量 (AV) | Network(网络) | 攻击者通过互联网远程发起攻击 |
| 攻击复杂度 (AC) | Low(低) | 攻击条件容易满足,构造一个XLSX文件即可 |
| 所需权限 (PR) | Low(低) | 需要具备上传文件的权限,但很多系统允许普通用户上传 |
| 影响范围 (S) | Changed(改变) | 影响范围跨越到服务器内网,这是最严重的部分 |
| 机密性影响 (C) | High(高) | 可完整读取内网服务响应和云凭证 |
再来看两个 DoS 漏洞的原理。CVE-2026-59933 攻击的是 OLE 文件格式的扇区链解析逻辑。OLE(Object Linking and Embedding)是微软的复合文档格式,.xls 文件就是基于 OLE 的。OLE 文件内部用「扇区链」来组织数据,每个扇区指向下一个扇区的位置,直到遇到终止标记 -2。漏洞在于 OLERead::read() 方法在遍历小数据块仓库(Small Block Depot)扇区链时没有检测循环。攻击者可以构造一个恶意的 .xls 文件,让扇区链形成自引用循环——某个扇区指向自己,于是 PHP 会反复读取同一个扇区的数据追加到字符串中,直到内存耗尽进程崩溃。修复方案是在遍历时维护一个已访问扇区列表,遇到重复就抛出异常。
CVE-2026-59932 攻击的是 Gnumeric 文件读取器。Gnumeric 是一个开源电子表格软件,其文件格式支持 gzip 压缩。当 PhpSpreadsheet 的 Gnumeric 读取器发现文件以 gzip 魔数(\x1f\x8b)开头时,会调用 gzdecode() 解压全部内容。但解压前没有检查解压后的数据大小上限。经典的 Gzip 炸弹技术可以将数百MB的数据压缩到几十KB,解压时瞬间膨胀。修复方案是读取 PHP 的 memory_limit 配置值,将解压大小限制为内存上限的四分之一,然后传入 gzdecode($contents, $this->maxLength) 的第二个参数来截断。
中国用户相关性分析:PhpSpreadsheet 是 PHP 生态中处理 Excel 文件的事实标准库。国内大量使用 Laravel、ThinkPHP、Webman 等框架开发的 SaaS 系统、电商后台、财务系统、OA 系统都通过 maatwebsite/excel(Laravel Excel 包,底层依赖 PhpSpreadsheet)来实现报表导入导出功能。特别需要注意的是,很多系统允许用户上传 Excel 文件来批量导入数据——员工名单、商品信息、订单数据等——这正是这些漏洞的攻击入口。部署在阿里云 ECS、腾讯云 CVM、华为云 ECS 上的 PHP 应用尤其危险,因为它们都暴露了 169.254.169.254 元数据服务,攻击者通过 SSRF 可以直接获取实例绑定的 RAM 角色临时凭证,进而横向移动到云平台控制台。
修复指南:分优先级行动
P0 紧急操作(立即执行):升级 PhpSpreadsheet 到对应分支的最新修复版本。这是唯一彻底解决问题的方案。
# 5.x 分支升级到 5.8.1
composer require phpoffice/phpspreadsheet:^5.8.1
# 3.x 分支升级到 3.10.7
composer require phpoffice/phpspreadsheet:^3.10.7
# 2.x 分支升级到 2.4.7
composer require phpoffice/phpspreadsheet:^2.4.7
# 1.x 分支升级到 1.30.6
composer require phpoffice/phpspreadsheet:^1.30.6
# 升级后清除缓存
composer dump-autoload
# 如果使用了 OPcache,重启 PHP-FPM
systemctl restart php8.3-fpm
P1 临时缓解(无法立即升级时):如果你暂时无法升级,但代码中使用了 WEBSERVICE 公式计算功能,可以在应用层手动修补 SSRF 漏洞。最简单的做法是在调用 getCalculatedValue() 之前,检查文件中是否包含 WEBSERVICE 公式,如果包含则拒绝计算或移除该公式。
// 临时缓解方案:扫描并移除 WEBSERVICE 公式
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile($file);
$spreadsheet = $reader->load($file);
foreach ($spreadsheet->getAllSheets() as $sheet) {
foreach ($sheet->getRowIterator() as $row) {
$cellIterator = $row->getCellIterator();
foreach ($cellIterator as $cell) {
$value = $cell->getValue();
if (is_string($value) && stripos($value, 'WEBSERVICE') !== false) {
$cell->setValue(''); // 移除危险公式
}
}
}
}
另一个方向是,如果你的业务不需要 WEBSERVICE 公式功能(大多数场景都不需要),可以考虑在应用层彻底禁用公式计算,只读取单元格的静态值。可以通过 getOldCalculatedValue() 来获取文件中缓存的计算结果,而不是重新计算公式:
// 读取缓存值而非重新计算,避免触发 WEBSERVICE 请求
$cellValue = $cell->getOldCalculatedValue() ?? $cell->getValue();
额外防护建议:首先,对文件上传增加严格的MIME 类型白名单校验,只允许 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet(XLSX)格式,拒绝 .xls(OLE 格式)和 .gnumeric 文件的上传,这样就能同时阻断两个 DoS 漏洞的攻击路径。其次,如果你的 PHP 应用部署在云服务器上,强烈建议配置云元数据服务的访问控制——阿里云可以通过 ECS 安全组规则限制对 169.254.169.254 的访问,AWS 可以启用 IMDSv2(要求 Token 认证),这样即使存在 SSRF,攻击者也拿不到云凭证。第三,考虑在文件解析层添加超时和内存限制,使用 set_time_limit() 和 ini_set('memory_limit', '256M') 来限制单个文件解析消耗的资源。第四,如果你使用了 Laravel Excel(maatwebsite/excel),它底层封装了 PhpSpreadsheet,需要确认其依赖的 PhpSpreadsheet 版本是否在受影响范围内,并在升级后运行 composer update phpoffice/phpspreadsheet 来拉取修复版本。
凭据轮换清单:如果你怀疑系统已经被利用,以下是需要立即轮换的凭据清单:第一,云平台的 RAM 角色临时凭证(STS Token),通过云控制台或 CLI 强制刷新;第二,如果元数据服务暴露了数据库连接字符串或 Redis 密码,立即更换这些服务的密码;第三,如果 PhpSpreadsheet 运行在容器中且 Pod 绑定了 ServiceAccount Token,在 Kubernetes 中重新生成该 ServiceAccount 的 Token;第四,检查云平台操作日志(阿里云 ActionTrail、AWS CloudTrail),排查是否有异常 API 调用。
总结:安全工程层面的教训
这组漏洞给我们带来了几个重要的安全工程教训。第一,缓解措施不等于安全控制。PhpSpreadsheet 在 5.4.0 引入域名白名单时,开发者可能认为这个功能已经解决了 SSRF 问题,但白名单只在请求的第一跳生效,后续的重定向完全没有覆盖。安全控制必须是端到端的,不能只在入口做一次检查就高枕无忧。第二,底层库的默认行为是安全盲区。PHP 的 file_get_contents() 默认跟随重定向,这个行为对大多数开发者来说是不可见的。当你把一个看似安全的函数组合在一起时,每个函数的默认行为可能叠加出意想不到的安全漏洞。第三,文件格式解析器是高风险区域。两个 DoS 漏洞都出在文件解析阶段——OLE 扇区链遍历和 Gzip 解压。解析不可信的文件格式时,必须对所有循环结构添加最大迭代次数限制和大小上限检查。
从更宏观的角度看,PhpSpreadsheet 这样的基础库被广泛依赖(Packagist 累计下载3.26亿次),一个 SSRF 漏洞可能影响数以万计的 PHP 应用。这也提醒我们,在架构设计中应该遵循纵深防御原则——不要把所有安全责任都压在一个库的白名单上,而要在网络层(安全组)、应用层(文件类型校验、公式过滤)、基础设施层(云元数据服务访问控制)都部署独立的安全控制。
行动清单:
- 立即执行
composer show phpoffice/phpspreadsheet 检查当前版本
- 如果版本低于修复版本,执行
composer update phpoffice/phpspreadsheet 升级
- 审查文件上传功能,确认是否允许 .xls 和 .gnumeric 格式,建议限制为只接受 .xlsx
- 如果使用了 WEBSERVICE 公式计算功能,确认是否调用了 setDomainWhiteList(),并检查白名单域名是否存在开放重定向
- 云服务器用户检查 169.254.169.254 元数据服务的访问控制策略
- 如果使用了 Laravel Excel,执行
composer show maatwebsite/excel 检查其依赖的 PhpSpreadsheet 版本
- 怀疑被利用的情况下,立即轮换云平台凭证并排查操作日志
参考信息:
- CVE-2026-59931 / CVE-2026-59932 / CVE-2026-59933,CVSS 7.7/7.5/7.5,CWE-918(SSRF)/CWE-400(资源消耗不受控)
- 官方安全公告:GHSA-6hq5-7373-42rg(SSRF)、GHSA-xh5m-36r6-47m3(OLE循环)、GHSA-2mrg-gjxq-2gvr(Gzip炸弹)
- 修复 Commit:7ef7b25e(SSRF)、85f2556b(DoS 两个)
- 受影响版本:4.0.0-5.8.0、3.3.0-3.10.6、2.2.0-2.4.6、2.0.0-2.1.17、1.30.5 及以下全部
- 修复版本:5.8.1 / 3.10.7 / 2.4.7 / 2.1.18 / 1.30.6
- 漏洞发现者:Thai Son Dinh(VinSOC Labs),oleibman(PhpSpreadsheet 维护者合并的社区报告)
- GitHub 项目:PHPOffice/PhpSpreadsheet,Stars 13,958,Packagist 累计下载3.26亿次
- 公布日期:2026年7月28日
龙虾池子