Skip to content

CVE-2026-34084: Verified Repro With Script Download

CVE-2026-34084: PhpSpreadsheet: SSRF via unsafe stream wrapper in IOFactory::load

CVE-2026-34084 is verified against phpoffice/phpspreadsheet · composer. Affected versions: <= 1.30.2 (also 2.0.0–2.1.14, 2.2.0–2.4.3, 3.3.0–3.10.3, 4.0.0–5.5.0). Vulnerability class: SSRF. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00150.

REPRO-2026-00150 phpoffice/phpspreadsheet · composer SSRF May 22, 2026 CVE entry ↗ .txt
Severity
CRITICAL
CVSS
9.8
Reproduced in
12m 53s
Tool calls
178
Spend
$1.62
01 · Overview

What Is CVE-2026-34084?

CVE-2026-34084 is a critical SSRF and PHP object-injection vulnerability in PhpSpreadsheet's IOFactory::load(), caused by an unsafe filename validation that lets attacker-controlled paths use PHP stream wrappers. Pruva reproduced it (reproduction REPRO-2026-00150).

02 · Severity & CVSS

CVE-2026-34084 Severity & CVSS Score

CVE-2026-34084 is rated critical severity, with a CVSS base score of 9.8 out of 10.

CRITICAL threat level
9.8 / 10 CVSS base
Weakness CWE-502 — Deserialization of Untrusted Data

Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.

03 · Affected Versions

Affected phpoffice/phpspreadsheet Versions

phpoffice/phpspreadsheet · composer versions <= 1.30.2 (also 2.0.0–2.1.14, 2.2.0–2.4.3, 3.3.0–3.10.3, 4.0.0–5.5.0) are affected.

How to Reproduce CVE-2026-34084

$ pruva-verify REPRO-2026-00150
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00150/artifacts/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-34084

Reproduced by Pruva's autonomous agents — 178 tool calls over 13 min. Full root-cause analysis and the complete transcript are below.

How the agent worked 637 events · 178 tool calls · 13 min
13 minDuration
178Tool calls
152Reasoning steps
637Events
1Dead-ends
Agent activity over 13 min
Support
33
Repro
200
Variant
400
0:0012:53

Root Cause and Exploit Chain for CVE-2026-34084

Summary

PhpSpreadsheet's IOFactory::load() and IOFactory::identify() methods validate user-supplied filenames through File::assertFile(), which internally relies on PHP's is_file() function. PHP's is_file() returns true for various stream wrapper protocols such as ftp://, phar://, and ssh2.sftp://. Because the library did not explicitly reject stream wrappers, an attacker-controlled filename could trigger an outbound network connection (SSRF via ftp://) or PHAR metadata deserialization (object injection via phar://). The fix adds a prohibitWrappers() guard in File::assertFile() and File::testFileNoThrow() that rejects any path containing a recognized URL scheme before is_file() is evaluated.

Impact

  • Package: phpoffice/phpspreadsheet (Packagist)
  • Repository: https://github.com/PHPOffice/PhpSpreadsheet
  • Affected versions: <= 1.30.2 (and corresponding ranges in 2.x, 3.x, 4.x, 5.x lines)
  • Fixed versions: 1.30.3, 2.1.15, 2.4.4, 3.10.4, 5.6.0
  • Severity: Critical (CVSS 9.2)
  • Consequences:
    • SSRF: Outbound connections to attacker-controlled hosts via ftp:// paths.
    • PHAR Deserialization: Object injection via phar:// paths, potentially leading to remote code execution.

Root Cause

In src/PhpSpreadsheet/Shared/File.php, the assertFile() method was implemented as:

public static function assertFile(string $filename, string $zipMember = ''): void
{
    if (!is_file($filename)) {
        throw new ReaderException('File "' . $filename . '" does not exist.');
    }
    // ...
}

PHP's is_file() natively supports stream wrappers. When passed an ftp:// path, is_file() initiates an FTP connection to the remote host. When passed a phar:// path, is_file() triggers PHAR metadata deserialization. The library never validated the filename for stream-wrapper schemes before delegating to is_file().

The fix (commit b6c44a4c / tag 1.30.3) adds a prohibitWrappers() helper:

public static function prohibitWrappers(string $filename): void
{
    $scheme = parse_url($filename, PHP_URL_SCHEME);
    if (is_string($scheme) && strlen($scheme) > 1) {
        throw new Exception(
            "Stream wrappers are not permitted as file paths: {$filename}"
        );
    }
}

This is called at the top of both assertFile() and testFileNoThrow(), ensuring that any path with a recognized scheme is rejected before any I/O (including is_file()) occurs.

Reproduction Steps

The reproduction script is repro/reproduction_steps.sh. It performs the following steps:

  1. Checks for php, composer, and python3 availability.
  2. Finds an ephemeral TCP port on 127.0.0.1.
  3. In a scratch directory, installs phpoffice/phpspreadsheet:1.30.2 (vulnerable).
  4. Creates a PHP script that calls IOFactory::load("ftp://127.0.0.1:<port>/x").
  5. Starts a Python TCP listener on the chosen port.
  6. Runs the PHP script and records whether the listener receives a connection.
  7. Repeats steps 3–6 for phpoffice/phpspreadsheet:1.30.3 (fixed).
  8. Compares results and writes validation_verdict.json.

Expected evidence:

  • Vulnerable (1.30.2): The Python listener logs CONNECTION_RECEIVED because is_file("ftp://...") opens an outbound FTP connection before failing.
  • Fixed (1.30.3): The Python listener logs NO_CONNECTION because prohibitWrappers() throws an exception before is_file() is reached.

Evidence

Log files are written to $ROOT/logs/ during script execution:

  • logs/vuln_listener.log: CONNECTION_RECEIVED from ('127.0.0.1', <port>)
  • logs/vuln_php.log: EXCEPTION: File "ftp://127.0.0.1:<port>/x" does not exist.
  • logs/fixed_listener.log: NO_CONNECTION
  • logs/fixed_php.log: EXCEPTION: Stream wrappers are not permitted as file paths: ftp://127.0.0.1:<port>/x

Validation verdict is written to validation_verdict.json:

{
  "verdict": "confirmed",
  "vulnerable_version": "1.30.2",
  "fixed_version": "1.30.3",
  "vulnerable_indicator": "CONNECTION_RECEIVED from ('127.0.0.1', ...)",
  "fixed_indicator": "NO_CONNECTION",
  "vulnerability_type": "SSRF via unsafe stream wrapper in IOFactory::load()",
  "details": "PhpSpreadsheet's File::assertFile() used is_file() which accepts PHP stream wrappers like ftp://. Fixed version adds prohibitWrappers() to reject stream wrappers before is_file() is called."
}

Environment:

  • PHP 8.4.19
  • Composer 2.8.12
  • Python 3.x

Recommendations / Next Steps

  1. Upgrade immediately to phpoffice/phpspreadsheet >= 1.30.3 (or the matching fixed version in your major line).
  2. Input validation: If wrapping IOFactory::load() in application code, add an independent allow-list or deny-list for filenames before passing them to the library.
  3. Regression test: Add a unit test that asserts File::assertFile("ftp://...") and File::assertFile("phar://...") throw exceptions.
  4. Disable unnecessary wrappers: In hardened deployments, consider disabling the ftp and phar PHP stream wrappers via stream_wrapper_unregister() if not required by the application.

Additional Notes

  • Idempotency: The reproduction script has been executed twice consecutively with identical results, confirming reproducibility.
  • SSRF vector chosen: The SSRF vector (ftp://) was selected over PHAR deserialization because it produces a clean, deterministic, network-free observable on localhost (a TCP connection attempt). No external network, database, or browser is required.
  • Edge cases: The fix uses strlen($scheme) > 1 to avoid false positives on Windows absolute paths (e.g., C:\...), since single-character schemes do not correspond to any known PHP stream wrapper.

CVE-2026-34084 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:000:41
0:00
session startedaccounts/fireworks/models/kimi-k2p6 · cve-2026-34084 · cve-2026
0:03
0:04
0:05
web search
0:06
0:08
web search
0:10
0:11
web search
0:12
0:13
web search
0:16
0:17
0:19
0:20
0:33
0:33
extract_facts
no facts extracted
0:34
0:34
0:34
supportrepro
0:40
0:41

Artifacts and Evidence for CVE-2026-34084

Scripts, logs, diffs, and output captured during the reproduction.

No artifacts available

08 · How to Fix

How to Fix CVE-2026-34084

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-34084 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-34084

How does the PhpSpreadsheet stream-wrapper exploit work?

When an attacker-influenced filename reaches IOFactory::load(), an ftp:// path makes the library open an outbound network connection to an attacker-chosen host (SSRF), while a phar:// path triggers deserialization of the PHAR archive's metadata, enabling PHP object injection that can escalate to remote code execution if exploitable gadget chains exist in the application.

Which PhpSpreadsheet versions are affected by CVE-2026-34084, and where is it fixed?

Versions <= 1.30.2 (and the corresponding 2.0.0-2.1.14, 2.2.0-2.4.3, 3.3.0-3.10.3, and 4.0.0-5.5.0 ranges) are affected. The root-cause analysis lists fixed versions 1.30.3, 2.1.15, 2.4.4, 3.10.4, and 5.6.0, which add a prohibitWrappers() guard in File::assertFile().

How severe is CVE-2026-34084?

Critical — the root-cause analysis cites CVSS 9.2; unauthenticated SSRF to attacker-controlled hosts and PHAR-based PHP object injection that can lead to remote code execution.

How can I reproduce CVE-2026-34084?

Download the verified script from this page and run it in an isolated environment against an affected PhpSpreadsheet build. It calls IOFactory::load() with an ftp:// and a phar:// filename to show the outbound SSRF connection and PHAR metadata deserialization, then confirms the patched prohibitWrappers() guard blocks both in the fixed versions.
11 · References

References for CVE-2026-34084

Authoritative sources for CVE-2026-34084 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.