{"id":"GHSA-c7r6-vx3h-w5g2","summary":"Laravel Excel writes exports outside the configured filesystem disk when given a caller-controlled path","details":"### Summary\n\n`Excel::store()` resolved the destination path against the process working\ndirectory rather than the configured filesystem disk. When that path resolved to\nan existing file, the export was written straight to it with `fopen()`,\nbypassing the disk entirely. An application that passes a user-controlled value\nas the export path could therefore be made to overwrite an arbitrary existing\nfile that the PHP process can write to, with content the user controls.\n\n### Details\n\n`Maatwebsite\\Excel\\Files\\Disk::copy()` contained two paths:\n\n```php\nif (realpath($destination)) {\n    $tempStream = fopen($destination, 'rb+');\n    $success    = stream_copy_to_stream($readStream, $tempStream) !== false;\n} else {\n    $success = $this-\u003eput($destination, $readStream);\n}\n```\n\n`$destination` is the `$filePath` argument given to `Excel::store()`,\n`$export-\u003estore()` or `-\u003estoreExcel()`. `realpath()` resolves it against the\n**current working directory** — `public/` for a typical web request — not\nagainst the disk root. On a hit, the write went directly to the filesystem and\nnever reached Flysystem, which would otherwise have rejected `../` traversal and\nconfined absolute paths to the disk root. The disk argument was effectively\nignored for those paths, including for remote disks such as S3.\n\nTwo consequences follow:\n\n* the destination could be any existing file the PHP process can write, in or\n  out of the disk root;\n* the stream was opened `'rb+'`, which does not truncate, so a shorter export\n  left trailing bytes of the previous file behind.\n\nBecause the file must already exist, the primitive is an **overwrite** rather\nthan an arbitrary file creation. Overwriting a PHP file that is reachable by the\nweb server (for example a front controller or a cached view) turns\nattacker-controlled row content into code execution, since CSV and HTML writers\nemit cell values verbatim. Passing an explicit writer type to `store()` bypasses\nthe extension-based type detection that would otherwise reject a `.php` target.\n\nExploitation requires the **application** to pass an unsanitized, user-controlled\nvalue as the export path. Applications that pass a fixed or server-derived path\nare not affected.\n\n### Impact\n\nArbitrary overwrite of existing files writable by the PHP process, with\npartially attacker-controlled content, leading to remote code execution where\nthe overwritten file is executed by the web server.\n\n### Patches\n\nFixed in **3.1.70**. `Disk::copy()` now always writes through the configured\nfilesystem disk, so Flysystem enforces the disk root for every export.\n\nNote the behaviour change: passing an absolute path to `store()` previously\nwrote to that path once the file existed. Paths now always resolve relative to\nthe disk root. Applications that relied on that should configure a disk rooted\nat the target location.\n\n### Workarounds\n\nFor anyone unable to upgrade, validate the path before passing it to `store()` —\nreject absolute paths and any `..` segment, or derive the filename server-side\nand never build it from request input:\n\n```php\n$name = basename($request-\u003einput('filename'));   // strips any directory part\nExcel::store($export, 'exports/' . $name, 'local');\n```\n\n### Credit\n\nReported responsibly by @seck19 via the contact address in `SECURITY.md`.","aliases":["CVE-2026-84374"],"modified":"2026-09-08T20:45:04.342404543Z","published":"2026-09-08T20:40:47Z","database_specific":{"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-09-08T20:40:47Z","nvd_published_at":"2026-09-01T22:17:19Z","cwe_ids":["CWE-22","CWE-73"]},"references":[{"type":"WEB","url":"https://github.com/SpartnerNL/Laravel-Excel/security/advisories/GHSA-c7r6-vx3h-w5g2"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-84374"},{"type":"WEB","url":"https://github.com/SpartnerNL/Laravel-Excel/commit/b5cafdfcf7ec63924e83303763be8fcae340f70b"},{"type":"PACKAGE","url":"https://github.com/SpartnerNL/Laravel-Excel"},{"type":"WEB","url":"https://github.com/SpartnerNL/Laravel-Excel/releases/tag/3.1.70"}],"affected":[{"package":{"name":"maatwebsite/excel","ecosystem":"Packagist","purl":"pkg:composer/maatwebsite/excel"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.1.8"},{"fixed":"3.1.70"}]}],"versions":["3.1.10","3.1.11","3.1.12","3.1.13","3.1.14","3.1.15","3.1.16","3.1.17","3.1.18","3.1.19","3.1.20","3.1.21","3.1.22","3.1.23","3.1.24","3.1.25","3.1.26","3.1.27","3.1.28","3.1.29","3.1.30","3.1.31","3.1.32","3.1.33","3.1.34","3.1.35","3.1.36","3.1.37","3.1.38","3.1.39","3.1.40","3.1.41","3.1.42","3.1.43","3.1.44","3.1.45","3.1.46","3.1.47","3.1.48","3.1.49","3.1.50","3.1.51","3.1.52","3.1.53","3.1.54","3.1.55","3.1.56","3.1.57","3.1.58","3.1.59","3.1.60","3.1.61","3.1.62","3.1.63","3.1.64","3.1.65","3.1.66","3.1.67","3.1.68","3.1.69","3.1.8","3.1.9"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-c7r6-vx3h-w5g2/GHSA-c7r6-vx3h-w5g2.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H"}]}