Kai Ole Hartwig
11 min read
High

PhpSpreadsheet CVE-2026-45034: Three Slashes Defeat the Phar Wrapper Guard — a Patch Bypass for CVE-2026-34084 with RCE Potential

CVE-2026-45034 (GHSA-87m4-826x-3crx, CVSS 4.0 9.2, CWE-502) is a complete bypass of the phar wrapper guard that PhpSpreadsheet introduced in response to CVE-2026-34084. The protection mechanism File::prohibitWrappers() checks filenames for dangerous stream wrapper schemes such as phar:// — but relies on the return behaviour of PHP's parse_url(). For paths with three or more slashes after the scheme, such as phar:///path/exploit.phar/dummy.csv, parse_url() returns false instead of a scheme string — the check is bypassed entirely, while PHP's stream layer still correctly resolves the URI as a phar wrapper. On PHP 7.x this can lead to automatic deserialization of phar metadata and thus remote code execution via object-injection gadget chains; on PHP 8.x it results in a file-read primitive. Affected are PhpSpreadsheet versions up to and including 1.30.4, fixed in version 1.30.5, released 7 Jun 2026.

TL;DR — 90 seconds

Affected?

Any PHP project with phpoffice/phpspreadsheet ≤ 1.30.4 as a Composer dependency that passes filenames — directly or indirectly — from user input to IOFactory::load() or related reader functions.

Risk?

Complete bypass of the phar wrapper guard from CVE-2026-34084 (CVSS 4.0 9.2, CWE-502). On PHP 7.x, potential remote code execution via phar deserialization and object-injection gadget chains; on PHP 8.x, a file-read primitive.

Immediate action?

composer require phpoffice/phpspreadsheet:^1.30.5 — or, if an update isn't possible right away, explicitly reject any filename input containing the substring :// before processing.

Recommendation?

Check your Composer dependency tree for transitive use of PhpSpreadsheet (e.g. via reporting/export bundles), explicitly set phar.readonly to On, and filter file uploads with an extension allowlist rather than a blocklist.

Criticality?

high — high CVSS score, a publicly documented bypass mechanism and proof-of-concept code exist, but no known active in-the-wild exploitation so far, and success depends on PHP version, phar.readonly configuration, and the availability of a usable gadget chain.

What is the problem?

PhpSpreadsheet is the de facto standard library for reading and writing Excel, CSV, and ODS files in PHP — it ships, directly or via wrapper packages, inside a large number of PHP frameworks, CMS extensions, and business applications, everywhere users can upload or download spreadsheets. In May 2026, CVE-2026-34084 became known: files smuggled in via the phar:// stream wrapper could trigger deserialization of phar metadata and potentially lead to remote code execution. In response, the maintainers introduced the File::prohibitWrappers() method, meant to check filenames for dangerous schemes such as phar://, php://, or zip:// before processing.

CVE-2026-45034 shows that this protection can be bypassed entirely. The cause lies in a quirk of PHP's built-in parse_url() function: when a URL contains three or more slashes directly after the scheme colon — for example phar:///path/exploit.phar/dummy.csv — parse_url() interprets this structure as invalid and returns false instead of extracting the expected string "phar" as the scheme. The validation code in prohibitWrappers() then checks with is_string($scheme) whether a scheme was detected at all — for false that's not the case, so the check is skipped entirely and the filename is passed through as harmless.

The problem: PHP's underlying stream layer uses its own, more robust URI resolution and still correctly interprets the same path as a phar:// wrapper access. The discrepancy between the (flawed) validation logic and the actual behaviour of the stream layer is the core of the bypass: what looks like an ordinary filename to prohibitWrappers() is still opened by PHP itself as a phar archive — including automatic processing of the attacker-controlled serialized metadata it contains.

Who is affected?

AffectedNot affected / unclearConditions
Projects with phpoffice/phpspreadsheet at version ≤ 1.30.4 (directly, or as a transitive dependency of a reporting/export package)Projects already updated to 1.30.5 or newerCheck the Composer lockfile — including indirect inclusion via other packages
Code paths that pass a (even partially) user-controlled filename or upload path to IOFactory::load(), IOFactory::createReaderForFile(), or similar reader functionsPurely internal processing of fixed, non-user-controlled file paths with no upload or URL input at allServer-generated paths assembled from user input also count as user-controlled
PHP 7.x environments, for the full RCE potential via phar object injectionPHP 8.x environments are, per current public analysis, limited to a file-read primitive — still serious, but not an automatic RCE pathThe actual impact on PHP 8.x further depends on phar.readonly and on gadget classes reachable via the autoloader

The vulnerability was published via the GitHub Advisory Database on 7 Jun 2026 as GHSA-87m4-826x-3crx, with CVSS 4.0 9.2 (Critical, CWE-502). No confirmed in-the-wild exploitation is documented as of this writing, but publicly available proof-of-concept code exists for this CVE — the technical details of the bypass are fully public, which increases the risk of near-term exploitation.

Impact

On PHP 7.x, the bypass opens the classic phar deserialization attack path: if an attacker-crafted .phar file is passed to PhpSpreadsheet via a filename matching the three-slash pattern described above, PHP automatically processes the serialized metadata contained in the phar stub upon access — regardless of whether the application actually expects or reads a spreadsheet file. If the target application's autoloader contains suitable classes with exploitable __wakeup() or __destruct() methods (a so-called gadget chain), this can result in remote code execution with the privileges of the PHP process — in many deployments meaning access to database credentials, API keys, and the application's filesystem.

On PHP 8.x, the immediate impact is, per current public knowledge, limited to a file-read primitive — the bypass still allows PhpSpreadsheet to read content outside the expected file context, which depending on the application can lead to disclosure of configuration files, credentials, or other sensitive files on the server. Because PhpSpreadsheet is frequently used in export/import functions of admin backends, reporting modules, and data migration workflows — contexts that already tend to carry elevated privileges and access to sensitive data — the potential blast radius of a successful attack is relevant regardless of PHP version.

Mitigation / immediate steps

Operational decision block

Step 1 — Update to the fixed version

 

# Composer update to the fixed version
composer require phpoffice/phpspreadsheet:^1.30.5

# Check which version is actually installed
composer show phpoffice/phpspreadsheet | grep -i versions

# If PhpSpreadsheet is only included transitively:
composer why phpoffice/phpspreadsheet

 

Step 2 — Short-term compensation if an update isn't immediately possible

 

# Input validation BEFORE every IOFactory::load() call:
# reject filenames containing the substring "://" outright,
# regardless of parse_url() results
if (str_contains($filename, '://')) {
    throw new InvalidArgumentException('Invalid filename');
}

# Additionally: only allow expected file extensions (allowlist, not blocklist)
$allowed = ['xlsx', 'xls', 'csv', 'ods'];
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (!in_array($ext, $allowed, true)) {
    throw new InvalidArgumentException('File type not allowed');
}

 

Step 3 — Harden PHP configuration

 

# Explicitly enforce phar.readonly (prevents writing new phar archives,
# but does NOT by itself protect against this deserialization bypass chain)
php -i | grep phar.readonly
# set explicitly in php.ini:
# phar.readonly = On

Detection / verification

Check installed version and usage

 

# Direct dependency?
grep -A2 '"phpoffice/phpspreadsheet"' composer.lock

# Transitive dependency via which package?
composer why phpoffice/phpspreadsheet

# Search the codebase for direct IOFactory calls
grep -rn "IOFactory::load(\|IOFactory::createReaderForFile(" --include="*.php" .

 

Search logs and uploads for suspicious filenames/paths

 

# Search access/application logs for the three-slash pattern
grep -E "phar:///" /var/log/*/access.log storage/logs/*.log 2>/dev/null
grep -E "[a-z]+:/{3,}" storage/logs/*.log 2>/dev/null

# Check upload directories for .phar files with unusual names
find /path/to/uploads -iname "*.phar*" -o -iname "*phar*"

 

Runtime indicators

Operator guidance

Mid-market

Check your Composer lockfile for phpoffice/phpspreadsheet — including transitive inclusion via reporting or export packages — and update to 1.30.5 promptly. Where an immediate update isn't possible, put the described :// substring check in front as compensation.

Enterprise

Additionally: identify all code paths that pass user-controlled filenames to PhpSpreadsheet or similar file-processing libraries, and where possible run that processing in isolated workers with minimal privileges and no access to production credentials. Explicitly set phar.readonly to On across all environments, and inventory PHP versions with an eye on remaining PHP 7.x installations — that's where the RCE potential is greatest.

All Composer projects with file uploads

Regardless of PhpSpreadsheet specifically: this case is a good example of why validation based on parse_url() alone isn't sufficient to reliably prevent stream wrapper abuse. Wherever filenames originate from user input, an additional strict extension allowlist and an explicit check for the substring :// should be applied — regardless of which library ultimately processes the file.

Decision block

Frequently asked questions about CVE-2026-45034

Does this also affect Laravel or Symfony projects that access PhpSpreadsheet via wrapper packages?+

Yes, provided the respective wrapper package internally bundles a vulnerable PhpSpreadsheet version. The check should therefore not be limited to direct composer.json entries, but should include the resolved composer.lock.

Is there already exploitation in the wild?+

As of current public knowledge, no confirmed active exploitation is documented. However, since the technical details and proof-of-concept code are publicly available, this should not be taken as reassurance.

Why is PHP 8.x less severely affected than PHP 7.x?+

PHP 8.x restricted automatic object deserialization from phar metadata in several ways, so per current public analysis the bypass results in a file-read primitive there rather than direct code execution. That's still serious, but not an automatic RCE path like on PHP 7.x.

Is my application affected even if I don't use PhpSpreadsheet directly?+

Possibly — many reporting, export, and data migration packages for PHP frameworks include PhpSpreadsheet transitively. Check with composer why phpoffice/phpspreadsheet whether and through which package a dependency exists.

Is a Composer update alone enough?+

For this specific flaw, yes — version 1.30.5 closes the described bypass. But since the root cause lies in relying on parse_url() for security decisions in general, it's worth adding your own validation of user-controlled filenames, independent of the library.

Conclusion

CVE-2026-45034 is a textbook example of how fragile security validation can be when it relies on the edge-case behaviour of a single function like parse_url() without cross-checking the actual interpretation performed by the underlying stream layer. The original fix for CVE-2026-34084 was well-intentioned but missed an edge case — with the result that the protection can be fully defeated with a single extra slash in the filename. For operators, this means: Composer updates are necessary, but for file-processing libraries they're often not sufficient on their own — an independent validation layer for user-controlled filenames remains worthwhile, precisely because even established, widely used libraries can overlook bypasses like this one.

Sources

I audit your Composer dependency tree for vulnerable PhpSpreadsheet versions, harden your file upload paths, and set up independent input validation.

Dependency audit for transitive PhpSpreadsheet usage, review of all code paths with user-controlled filenames, setting up an extension allowlist, and PHP hardening — so a single overlooked bypass doesn't become a security incident.

Platform operations, not consulting on paper: I continuously audit, harden, and monitor your PHP infrastructure.

Book a call →

About the author

[Translate to English:] Foto von Kai Ole Hartwig.

Kai Ole Hartwig

Freelance DevSecOps consultant · OnlyOle Consulting

Programming since 2002 – self-taught, set up my own business with KO-Web in 2012. Over 100 projects, with a focus on security, performance, automation and quality. Today freelance: DevSecOps consulting, training and software development.