{"id":"CVE-2026-84374","aliases":["EUVD-2026-69706","GHSA-c7r6-vx3h-w5g2"],"status":"published","title":"Laravel Excel writes exports outside the configured filesystem disk when given a caller-controlled path","description":"Laravel Excel provides supercharged Excel exports and imports in Laravel. From 3.1.8 until 3.1.70, in src/Files/Disk.php the Maatwebsite\\Excel\\Files\\Disk::copy() method resolves the caller-controlled $destination supplied through Excel::store(), $export-\u003estore(), or storeExcel() against the process working directory with realpath() instead of the configured filesystem disk. If the path names an existing writable file, Disk::copy() opens it with fopen() in rb+ mode and uses stream_copy_to_stream(), bypassing Flysystem path confinement and allowing an attacker whose application input controls the export path to overwrite arbitrary existing files with export content. The rb+ behavior creates a non-truncating overwrite and trailing bytes when the new export is shorter, and overwriting an executable PHP file can lead to remote code execution. This issue is fixed in version 3.1.70.","severity":{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00842,"percentile":0.58353,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-22","CWE-73"],"published":"2026-09-01T21:28:30Z","modified":"2026-10-28T02:31:14Z","fixes":[{"ecosystem":"Packagist","package":"maatwebsite/excel","fixed_in":["3.1.70"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Packagist","package":"maatwebsite/excel","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"3.1.8"},{"fixed":"3.1.70"}]}]},{"source":"osv","ecosystem":"Packagist","package":"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"]}],"products":[{"vendor":"spartnernl","product":"laravel-excel"}],"references":[{"url":"https://github.com/SpartnerNL/Laravel-Excel/security/advisories/GHSA-c7r6-vx3h-w5g2","tags":["x_refsource_CONFIRM","advisory","web"]},{"url":"https://github.com/SpartnerNL/Laravel-Excel/commit/b5cafdfcf7ec63924e83303763be8fcae340f70b","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/SpartnerNL/Laravel-Excel/releases/tag/3.1.70","tags":["x_refsource_MISC","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-84374","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-c7r6-vx3h-w5g2"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/84xxx/CVE-2026-84374.json","tags":["advisory"]},{"url":"https://github.com/SpartnerNL/Laravel-Excel","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-84374","title":"Laravel Excel writes exports outside the configured filesystem disk when given a caller-controlled path","description":"Laravel Excel provides supercharged Excel exports and imports in Laravel. From 3.1.8 until 3.1.70, in src/Files/Disk.php the Maatwebsite\\Excel\\Files\\Disk::copy() method resolves the caller-controlled $destination supplied through Excel::store(), $export-\u003estore(), or storeExcel() against the process working directory with realpath() instead of the configured filesystem disk. If the path names an existing writable file, Disk::copy() opens it with fopen() in rb+ mode and uses stream_copy_to_stream(), bypassing Flysystem path confinement and allowing an attacker whose application input controls the export path to overwrite arbitrary existing files with export content. The rb+ behavior creates a non-truncating overwrite and trailing bytes when the new export is shorter, and overwriting an executable PHP file can lead to remote code execution. This issue is fixed in version 3.1.70.","status":"published","published":"2026-09-01T21:28:30.355Z","modified":"2026-09-04T02:14:57.257Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"cna"}],"cwes":["CWE-22","CWE-73"],"updated_at":"2026-11-02T14:20:08.734143Z"}],"euvd":[{"id":"EUVD-2026-69706","description":"Laravel Excel provides supercharged Excel exports and imports in Laravel. From 3.1.8 until 3.1.70, in src/Files/Disk.php the Maatwebsite\\Excel\\Files\\Disk::copy() method resolves the caller-controlled $destination supplied through Excel::store(), $export-\u003estore(), or storeExcel() against the process working directory with realpath() instead of the configured filesystem disk. If the path names an existing writable file, Disk::copy() opens it with fopen() in rb+ mode and uses stream_copy_to_stream(), bypassing Flysystem path confinement and allowing an attacker whose application input controls the export path to overwrite arbitrary existing files with export content. The rb+ behavior creates a non-truncating overwrite and trailing bytes when the new export is shorter, and overwriting an executable PHP file can lead to remote code execution. This issue is fixed in version 3.1.70.","status":"published","published":"2026-09-01T21:28:30Z","modified":"2026-09-04T02:14:57Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"updated_at":"2026-11-02T18:01:13.160542Z"}],"ghsa":[{"id":"GHSA-c7r6-vx3h-w5g2","title":"Laravel Excel writes exports outside the configured filesystem disk when given a caller-controlled path","description":"### 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`.","status":"published","published":"2026-09-08T20:40:47Z","modified":"2026-09-08T20:40:48Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-22","CWE-73"],"updated_at":"2026-11-02T14:41:14.764963Z"}],"nvd":[{"id":"CVE-2026-84374","description":"Laravel Excel provides supercharged Excel exports and imports in Laravel. From 3.1.8 until 3.1.70, in src/Files/Disk.php the Maatwebsite\\Excel\\Files\\Disk::copy() method resolves the caller-controlled $destination supplied through Excel::store(), $export-\u003estore(), or storeExcel() against the process working directory with realpath() instead of the configured filesystem disk. If the path names an existing writable file, Disk::copy() opens it with fopen() in rb+ mode and uses stream_copy_to_stream(), bypassing Flysystem path confinement and allowing an attacker whose application input controls the export path to overwrite arbitrary existing files with export content. The rb+ behavior creates a non-truncating overwrite and trailing bytes when the new export is shorter, and overwriting an executable PHP file can lead to remote code execution. This issue is fixed in version 3.1.70.","status":"published","published":"2026-09-01T22:17:19.293Z","modified":"2026-09-09T21:09:13.08Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high","assessor":"secondary"}],"cwes":["CWE-22","CWE-73"],"updated_at":"2026-11-02T14:12:43.04945Z"}],"osv":[{"id":"CVE-2026-84374","title":"Laravel Excel writes exports outside the configured filesystem disk when given a caller-controlled path","description":"Laravel Excel provides supercharged Excel exports and imports in Laravel. From 3.1.8 until 3.1.70, in src/Files/Disk.php the Maatwebsite\\Excel\\Files\\Disk::copy() method resolves the caller-controlled $destination supplied through Excel::store(), $export-\u003estore(), or storeExcel() against the process working directory with realpath() instead of the configured filesystem disk. If the path names an existing writable file, Disk::copy() opens it with fopen() in rb+ mode and uses stream_copy_to_stream(), bypassing Flysystem path confinement and allowing an attacker whose application input controls the export path to overwrite arbitrary existing files with export content. The rb+ behavior creates a non-truncating overwrite and trailing bytes when the new export is shorter, and overwriting an executable PHP file can lead to remote code execution. This issue is fixed in version 3.1.70.","status":"published","published":"2026-09-01T21:28:30.355Z","modified":"2026-10-28T02:31:14.423378251Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-22","CWE-73"],"updated_at":"2026-11-02T15:45:18.482949Z"},{"id":"GHSA-c7r6-vx3h-w5g2","title":"Laravel Excel writes exports outside the configured filesystem disk when given a caller-controlled path","description":"### 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`.","status":"published","published":"2026-09-08T20:40:47Z","modified":"2026-09-08T20:45:04.342404543Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H","level":"high"}],"cwes":["CWE-22","CWE-73"],"updated_at":"2026-11-02T15:46:44.571161Z"}]}}
