# Grav: Path Traversal in MediaUploadTrait::deleteFile() Allows Arbitrary File Deletion

Grav's MediaUploadTrait::deleteFile() validates only the basename, so an authenticated media editor can traverse out of the media directory and unlink arbitrary files.

- Payout: Research
- Severity: high
- Program: Grav
- Found by: ka3n1x
- Disclosed: 2026-09-17
- CVE: CVE-2026-72695
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-jq29-c7v8-rg55
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-jq29-c7v8-rg55
- Tags: area:web domain:web impact:dos impact:integrity impact:privesc kind:research platform:ghsa sev:high tech:php technique:source-review vuln:access-control vuln:path-traversal

**Target:** PHP CMS media upload/delete handler (MediaUploadTrait via Flex media pipeline)
**Root cause:** deleteFile() applies Utils::checkFilename() only to Filesystem::basename($filename), then reconstructs the pathname from the unvalidated directory portion and passes it to unlink().
**Steps:**
1. Create target: `echo "DELETE_ME" > /var/www/grav/user/data/target.txt`
2. Submit page edit form `POST /admin/pages/mypage/task:save` with flash media deletion entry keyed `../../data/target.txt` -> null
3. FlexMediaTrait::setUpdatedMedia() stores raw filename in `_uploads` (no upload validation for deletions since `$file` is null)
4. saveUpdatedMedia() calls `deleteFile("../../data/target.txt")`; basename `target.txt` passes checkFilename()
5. doRemove() runs `unlink("/var/www/grav/user/pages/mypage/../../data/target.txt")`, deleting the file outside media dir
**How found:** Code review of deleteFile() showing checkFilename() applied to basename only while the directory portion is preserved into the unlink() path; traced the Flex media pipeline to confirm user-controlled filename keys reach it unvalidated for deletions.
**Exploitation:** Authenticated admin user with page/media edit permissions (not super-admin) submits a crafted media deletion key containing `../` sequences; the traversal survives basename validation and unlink() removes arbitrary files such as config, other pages' content, or user account YAMLs.
**Escalation:** Deleting security/config YAML files or user account files may enable DoS or privilege escalation.
**Insight:** When a sanitizer is applied to basename() before path reconstruction, the directory component escapes validation — grep for basename()+checkFilename()/sanitize patterns feeding unlink(), rename() or file writes, and check sibling methods (renameFile had no check at all).

