# Laravel Excel writes exports outside the configured filesystem disk when given a caller-controlled path

Laravel Excel's Disk::copy() resolved export paths via realpath() against the CWD, letting caller-controlled paths overwrite arbitrary existing writable files and achieve RCE.

- Payout: Research
- Severity: high
- Program: Laravel Excel
- Found by: seck19
- Disclosed: 2026-09-08
- CVE: CVE-2026-84374
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-c7r6-vx3h-w5g2
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-c7r6-vx3h-w5g2
- Tags: area:web domain:web impact:integrity impact:rce kind:research platform:ghsa sev:high tech:php technique:source-review vuln:file-upload vuln:path-traversal vuln:rce

**Target:** PHP library export storage (Excel::store / Disk::copy)
**Root cause:** Disk::copy() used realpath($destination) which resolves against the process CWD instead of the configured disk root; on a hit it fopen'd the file directly, bypassing Flysystem's traversal confinement.
**Steps:**
1. Application passes user-controlled value as $filePath to Excel::store()/->store()/->storeExcel().
2. Attacker supplies a path resolving (via realpath against CWD, e.g. public/) to an existing writable file such as a PHP front controller or cached view.
3. Pass an explicit writer type to store() to bypass extension-based type detection rejecting .php.
4. Export row content (CSV/HTML writers emit cell values verbatim) is stream_copy_to_stream'd into the target with `fopen($destination, 'rb+')`, overwriting it without truncation.
5. Request the overwritten PHP file to execute attacker-controlled content.
**How found:** Code review of Disk::copy() revealed the realpath() branch resolving against CWD rather than the disk root, and the 'rb+' fopen bypassing Flysystem.
**Exploitation:** Requires the app to pass unsanitized user input as the export path. Attacker targets an existing PHP file writable by the PHP process, supplies it as the path plus an explicit writer type, and injects PHP code into export cell values; the non-truncating 'rb+' write leaves trailing bytes but the injected code executes when the file is requested.
**Escalation:** Overwriting a web-reachable PHP file (front controller, cached view) converts file overwrite into remote code execution.
**Insight:** When a library has a 'fast path' that calls realpath()/fopen() directly before falling back to an abstraction like Flysystem, check what base directory realpath resolves against — CWD differs from the disk root and silently escapes confinement. Also note 'rb+' doesn't truncate, so shorter payloads leave stale trailing bytes.

