GHSA-c7r6-vx3h-w5g2: Path Traversal

Published Sep 8, 2026
·
Updated

Summary

Excel::store() resolved the destination path against the process working directory rather than the configured filesystem disk. When that path resolved to an existing file, the export was written straight to it with fopen(), bypassing the disk entirely. An application that passes a user-controlled value as the export path could therefore be made to overwrite an arbitrary existing file that the PHP process can write to, with content the user controls.

Details

Maatwebsite\Excel\Files\Disk::copy() contained two paths:

php if (realpath($destination)) { $tempStream = fopen($destination, 'rb+'); $success = streamcopytostream($readStream, $tempStream) !== false; } else { $success = $this->put($destination, $readStream); }

$destination is the $filePath argument given to Excel::store(), $export->store() or ->storeExcel(). realpath() resolves it against the current working directory — public/ for a typical web request — not against the disk root. On a hit, the write went directly to the filesystem and never reached Flysystem, which would otherwise have rejected ../ traversal and confined absolute paths to the disk root. The disk argument was effectively ignored for those paths, including for remote disks such as S3.

Two consequences follow:

the destination could be any existing file the PHP process can write, in or out of the disk root; the stream was opened 'rb+', which does not truncate, so a shorter export left trailing bytes of the previous file behind.

Because the file must already exist, the primitive is an overwrite rather than an arbitrary file creation. Overwriting a PHP file that is reachable by the web server (for example a front controller or a cached view) turns attacker-controlled row content into code execution, since CSV and HTML writers emit cell values verbatim. Passing an explicit writer type to store() bypasses the extension-based type detection that would otherwise reject a .php target.

Exploitation requires the application to pass an unsanitized, user-controlled value as the export path. Applications that pass a fixed or server-derived path are not affected.

Impact

Arbitrary overwrite of existing files writable by the PHP process, with partially attacker-controlled content, leading to remote code execution where the overwritten file is executed by the web server.

Patches

Fixed in 3.1.70. Disk::copy() now always writes through the configured filesystem disk, so Flysystem enforces the disk root for every export.

Note the behaviour change: passing an absolute path to store() previously wrote to that path once the file existed. Paths now always resolve relative to the disk root. Applications that relied on that should configure a disk rooted at the target location.

Workarounds

For anyone unable to upgrade, validate the path before passing it to store() — reject absolute paths and any .. segment, or derive the filename server-side and never build it from request input:

php $name = basename($request->input('filename')); // strips any directory part Excel::store($export, 'exports/' . $name, 'local');

Credit

Reported responsibly by @seck19 via the contact address in SECURITY.md.

Affected Software

1 affected componentFixes available
composer/maatwebsite/excel>=3.1.8<3.1.70
3.1.70

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade composer/maatwebsite/excel to a version that resolves this vulnerability.

    Fixed in 3.1.70
  2. Upgrade

    Upgrade Maatwebsite Excel to a version that resolves this vulnerability.

    Fixed in 3.1.70
  3. Configuration

    For any export call like `$export->store()` / `Excel::store(..., $diskOrPath)` / `->storeExcel()`, validate the destination path before passing it to `store()`: never pass absolute paths or paths containing `..`; build/derive the export filename server-side (do not use `request->input('filename')` or other user-controlled values directly).

    PHP (Excel::store / writer invocation) Path validation for export destination = Reject absolute paths and any `..` segment; derive filename server-side (do not use request input)
  4. Configuration

    If you cannot upgrade to 3.1.70, avoid bypassing the filesystem disk: pass the export destination using the configured filesystem disk (and use an explicit writer type as described) so Flysystem enforces the disk root for every export and prevents escaping it.

    Maatwebsite\Excel (store destination handling) Disk writer argument usage = Use explicit writer type / disk so Flysystem enforces the disk root for every export

Event History

Sep 8, 2026
Advisory Published
via GitHub·08:40 PM
Data Sourced
via GitHub·08:40 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Which applications are exposed to this issue?

Applications are exposed if they pass a user-controlled value as the file path to Excel::store(), $export->store(), or storeExcel(). The issue can overwrite an existing file that the PHP process is permitted to write.

2

What conditions must an attacker satisfy to exploit it?

The attacker needs to control the export destination path and select a path that resolves to an existing file. The PHP process must have write permission to that target, and the attacker controls the export content written to it.

3

Does choosing a configured filesystem disk prevent the overwrite?

Not when the supplied destination resolves to an existing file. In that case, the code opens the resolved filesystem path directly with fopen() and bypasses Flysystem and the configured disk's path restrictions.

4

Where are affected paths resolved during a typical web request?

The destination is resolved relative to the PHP process current working directory rather than the configured disk root. For a typical web request, the working directory is described as public/.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203