PhpSpreadsheet is one of the most-deployed PHP libraries in the world: used by millions of sites to import/export Excel, OpenDocument, and CSV. CVE-2026-34084 (CVSS 9.8) reveals that the IOFactory::load() function accepts PHP wrapper paths (phar://, ftp://, ssh2.sftp://) that pass the File::assertFile() check. The phar:// wrapper triggers PHAR metadata deserialization — a classic path to PHP RCE. The ftp:// and ssh2.sftp:// wrappers open the door to server-side SSRF.
For any site that passes user-controlled data (file path, import URL, upload parameter) to IOFactory::load(), this is an open highway to full compromise.
Technical Details
Vulnerable component
The IOFactory::load(string $filename, ...) method is the standard entry point for loading a spreadsheet file. It first calls File::assertFile($filename) to verify the path is valid. This check uses is_file() — which returns true for PHP wrapper paths like phar://, ftp://, ssh2.sftp://.
Once the check passes, PhpSpreadsheet opens the file via standard PHP functions. If the path is phar://archive.phar, PHP automatically deserializes the PHAR archive metadata, which can invoke magic methods (__wakeup, __destruct) on objects whose class is defined in the application code.
This is the classic PHP gadget chain pattern: if the application contains a class with an exploitable magic method (e.g., eval($this->code) in __destruct), the attacker gains arbitrary code execution.
Characteristics
| Field | Value |
|---|---|
| CVSS 3.1 | 9.8 (CRITICAL) |
| Vector | AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-502 (Deserialization of Untrusted Data) + CWE-918 (SSRF) |
| Prerequisite | IOFactory::load()'s $filename parameter influenced by user input |
| Prerequisite #2 for RCE | Presence of a gadget chain in the code (often provided by Laravel/Symfony/Yii/CodeIgniter frameworks and their dependencies) |
Affected Products and Versions
| Branch | Affected versions | Patched version |
|---|---|---|
| 1.x | ≤ 1.30.2 | 1.30.3 |
| 2.0.x | 2.0.0 → 2.1.14 | 2.1.15 |
| 2.2.x → 2.4.x | 2.2.0 → 2.4.3 | 2.4.4 |
| 3.3.x → 3.10.x | 3.3.0 → 3.10.3 | 3.10.4 |
| 4.x → 5.x | 4.0.0 → 5.5.0 | 5.6.0 |
Check your version:
composer show phpoffice/phpspreadsheet | grep versions
Exploitation and Impact
Attack scenarios
Scenario 1 — User-supplied Excel import
An application lets users upload an Excel file for processing. The controller receives the stored path and calls IOFactory::load($uploadedFile). The attacker uploads a legitimate-looking file but named evil.phar (or via path traversal forces the phar:// wrapper).
Scenario 2 — Controlled URL parameter
The application accepts a ?file=... parameter to generate a report from a template. The attacker supplies ?file=phar:///tmp/uploaded.phar and triggers deserialization.
Scenario 3 — SSRF via ftp/ssh2.sftp
Even without a working RCE gadget chain, an attacker can use ftp://internal-service:21/file.xlsx to make the PHP server issue requests to internal services unreachable from the internet — typical for attacking a Redis instance, a cloud metadata service (AWS IMDS), or an internal dashboard.
PoC concept (RCE via phar)
// Attacker side — craft the PHAR with gadget chain
$phar = new Phar('evil.phar');
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($gadgetChainObject); // target's gadget
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
// evil.phar is then uploaded to the target
// Target side (vulnerable)
$file = $_GET['file']; // attacker controlled
\PhpOffice\PhpSpreadsheet\IOFactory::load($file);
// if $file = 'phar:///tmp/evil.phar', the gadget chain runs
Impact
- Full RCE in the PHP process (often www-data or the application account)
- SSRF toward unexposed internal services
- File read via
phar://pointing at arbitrary paths - LAN pivot from the web server
Detection and IOCs
Web server logs
Search access logs for parameters containing phar://, ftp://, ssh2.sftp://:
grep -E "phar://|ftp://|ssh2\.sftp://" /var/log/apache2/access.log
PHP logs
Enable error logging and hunt for:
- Deserialization warnings
PharExceptionerrors- Fatal errors in
__wakeup,__destruct
grep -E "Phar|unserialize|__wakeup|__destruct" /var/log/php/error.log
Application code audit
Locate every call to IOFactory::load():
grep -rn "IOFactory::load" /var/www/your-app/
For each call, verify:
- Is
$filenamederived from user input (POST, GET, COOKIE, header)? - Is there explicit prefix validation (reject
phar://, etc.)?
WAF rule (ModSecurity)
SecRule ARGS "@rx (phar|ftp|ssh2\.sftp)://" \
"id:'2026034084',phase:2,deny,status:403,\
msg:'PhpSpreadsheet CVE-2026-34084 wrapper injection attempt',\
t:lowercase"
Mitigation and Patch
Immediate action: upgrade
# Composer
composer require phpoffice/phpspreadsheet:^5.6.0
# Or for older branches:
composer require phpoffice/phpspreadsheet:^3.10.4
composer require phpoffice/phpspreadsheet:^2.4.4
composer require phpoffice/phpspreadsheet:^2.1.15
composer require phpoffice/phpspreadsheet:^1.30.3
Workaround if upgrading is delayed
- Application-side validation before every
IOFactory::load()call:
function isSafeSpreadsheetPath(string $path): bool {
// Reject all PHP wrappers
$forbidden = ['phar://', 'ftp://', 'ssh2.sftp://', 'http://', 'https://',
'data://', 'php://', 'zip://', 'compress.zlib://'];
foreach ($forbidden as $prefix) {
if (stripos($path, $prefix) === 0) return false;
}
// Force an absolute path within a trusted directory
$real = realpath($path);
return $real !== false && str_starts_with($real, '/var/uploads/');
}
if (!isSafeSpreadsheetPath($filename)) {
throw new InvalidArgumentException("Unsafe path");
}
\PhpOffice\PhpSpreadsheet\IOFactory::load($filename);
- Disable the PHAR extension in
php.iniif your application doesn't use it:
disable_functions = phar_loader
; or remove the phar extension from loading
- WAF with PHP-wrapper input filtering
Long-term hardening
- Audit every PHP library that calls
is_file(),file_exists(),file_get_contents()on user-controlled paths (PhpSpreadsheet isn't the only one with this pattern) - Block
phar://,ftp://, etc. in parameters at the Apache/Nginx layer - Rotate application credentials if you suspect past exploitation
- Audit PHP logs for old deserialization warnings
Why Continuous Monitoring of Application Dependencies Matters
PhpSpreadsheet is one of those dependencies you install once and forget — yet it ships in thousands of PHP projects, including CMSes, internal tools, and WordPress plugins. A CVE like CVE-2026-34084 instantly affects all those deployments, but few IT departments have visibility into their third-party dependencies.
With cveo.tech, inventory your PHP applications and their key dependency versions and get automatic alerts the moment a critical CVE targets one of your exact versions — including buried components in the composer.lock that nobody looks at anymore.