{"id":"GHSA-44r8-3p76-84mw","summary":"FileGator: Missing authorization in POST /upload allows cross-user read and deletion of temporary files","details":"## Summary\n\n`POST /upload` uses the request-supplied `resumableFilename` directly as a key into the shared temporary directory, without the character filter it applies to `resumableIdentifier` and without namespacing it per user. A valid user holding the `upload` permission can read any file in that directory into their own home directory, append to it, and delete it.\n\nThis reproduces the cross-user batch-archive disclosure of GHSA-f74m-x83r-c4v4 through a different route — it still works with the fix for that advisory (PR #597) applied — and additionally allows deleting the login rate-limit lockfile.\n\nReported with explicit preconditions and bounded impact, as before: the primitive is confined to the temporary directory, not the whole filesystem.\n\n## Details\n\n- `backend/Controllers/UploadController.php:61` — `$file_name = $request-\u003einput('resumableFilename', 'file')` is **not** passed through the `preg_replace('/[^0-9a-zA-Z_]/', '', ...)` that line 66 applies to `resumableIdentifier`.\n- Line 88 builds `$prefix` namespaced by the sanitized username, but `$file_name` reaches the temporary storage at lines 118, 121 and 125 **without** that prefix.\n- Line 115 — `if ($chunks_size \u003e= $total_size)` is entered whenever `resumableTotalSize` is `0`.\n- Lines 116-119 — the assembly loop `for ($i = 1; $i \u003c= $total_chunks; ++$i)` does not execute when `resumableTotalChunks` is `0`, so nothing is written to `$file_name`.\n- Line 121 — `readStream($file_name)` therefore reads the pre-existing temporary file of that exact name.\n- Line 122 — its contents are stored into the requester's own home directory.\n- Line 125 — `remove($file_name)` deletes it.\n\nPath traversal outside the temporary directory is prevented by `Tmpfs::sanitizeFilename()`, so the primitive is bounded to that directory — but every user's transient objects share it.\n\n## PoC\n\nPreconditions: a multi-user deployment with distinct per-user `homedir`s. The attacker is a valid low-privileged user holding `upload` (a first-class permission in the admin UI, and present in the shipped `users.json.blank` admin set).\n\n1. Victim creates a batch archive via `POST /batchdownload`; the JSON response returns the archive id.\n2. Attacker sends **one** `POST /upload` with `resumableFilename=\u003cvictim's id\u003e`, `resumableTotalChunks=0`, `resumableTotalSize=0`, `resumableRelativePath=/`, and any small valid file part.\n3. Response is `{\"data\":\"Stored\"}`. The victim's archive is now byte-identical in the attacker's home directory, retrievable through the ordinary `GET /download`. The victim's temporary archive has been deleted.\n\nVerified end to end with a PHPUnit test against current `master`; the archive contents matched byte for byte. Test file available on request.\n\n## Impact\n\nMissing authorization (CWE-862) on temporary storage objects.\n\n- **Cross-user disclosure** of any transient object whose name the attacker can learn or predict. This includes batch archives — so GHSA-f74m-x83r-c4v4's impact remains reachable even with its fix applied — and the plaintext per-file copies that `ZipArchiver::addFileFromStorage()` writes while an archive is being built (those still use `uniqid()`, which is timestamp-derived).\n- **Arbitrary delete** within the temporary directory. `AuthController::login()` stores the login rate-limit counter as `md5($ip).'.lock'` in that same directory (`backend/Controllers/AuthController.php:36`, checked at line 44, written at line 58). An attacker can compute their own lockfile name and delete it, defeating the lockout.\n- **Arbitrary append**: with `resumableTotalChunks \u003e= 1`, line 118 `write($file_name, ..., true)` appends attacker-controlled bytes to an arbitrary existing temporary file.\n\n## Remediation\n\nDo not use the request-supplied filename as a storage key. Namespace the assembly file per user — for example reuse the existing `$prefix` — and carry the display filename separately for the `storage-\u003estore()` call.\n\nNote that simply applying the `[^0-9a-zA-Z_]` filter to `resumableFilename` would corrupt legitimate uploads (`sample.txt` becomes `sampletxt`), because `$final['filename']` is what becomes the stored name. The fix needs the namespacing, not just the filter.","aliases":["CVE-2026-94619"],"modified":"2026-11-09T22:30:04.103841250Z","published":"2026-11-09T22:20:08Z","database_specific":{"cwe_ids":["CWE-862"],"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-11-09T22:20:08Z","nvd_published_at":"2026-11-06T22:18:00Z"},"references":[{"type":"WEB","url":"https://github.com/filegator/filegator/security/advisories/GHSA-44r8-3p76-84mw"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-94619"},{"type":"WEB","url":"https://github.com/filegator/filegator/pull/598"},{"type":"WEB","url":"https://github.com/filegator/filegator/commit/cae442a8323fddae7c8cee0924cdbb1aa6eea702"},{"type":"PACKAGE","url":"https://github.com/filegator/filegator"},{"type":"WEB","url":"https://github.com/filegator/filegator/releases/tag/v7.15.0"}],"affected":[{"package":{"name":"filegator/filegator","ecosystem":"Packagist","purl":"pkg:composer/filegator/filegator"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.15.0"}]}],"versions":["v7.0.0","v7.0.0-RC1","v7.0.0-RC2","v7.0.0-RC3","v7.0.1","v7.1.0","v7.1.1","v7.1.2","v7.1.3","v7.1.4","v7.1.5","v7.1.6","v7.10.0","v7.10.1","v7.11.0","v7.11.1","v7.12.0","v7.13.0","v7.13.1","v7.13.2","v7.13.3","v7.13.4","v7.13.5","v7.14.0","v7.14.1","v7.14.2","v7.14.3","v7.14.4","v7.2.0","v7.2.1","v7.3.0","v7.3.1","v7.3.2","v7.3.3","v7.3.4","v7.3.5","v7.4.0","v7.4.1","v7.4.2","v7.4.3","v7.4.4","v7.4.5","v7.4.6","v7.4.7","v7.5.0","v7.5.1","v7.5.2","v7.5.3","v7.6.0","v7.7.0","v7.7.1","v7.7.2","v7.8.0","v7.8.1","v7.8.2","v7.8.3","v7.8.4","v7.8.5","v7.8.6","v7.8.7","v7.9.0","v7.9.1","v7.9.2","v7.9.3"],"database_specific":{"last_known_affected_version_range":"\u003c= 7.14.4","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/11/GHSA-44r8-3p76-84mw/GHSA-44r8-3p76-84mw.json"}}],"schema_version":"1.9.2","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:L/A:L"}]}