# Grav: Unauthenticated Path Traversal via Missing Directory-Boundary Check in `plugin-asset-map.php` Static Asset Server (`index.php`)

Grav's pre-boot static asset server used a plain string-prefix containment check, letting unauthenticated attackers read sibling directories like assets-secret via ../ traversal.

- Payout: Research
- Severity: high
- Program: Grav
- Found by: alham-rizvi
- Disclosed: 2026-09-17
- CVE: CVE-2026-74907
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-4v9q-p283-qc2m
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-4v9q-p283-qc2m
- Tags: area:web domain:web impact:data-leak kind:research platform:ghsa sev:high tech:php technique:source-review vuln:info-disclosure vuln:path-traversal

**Target:** PHP front controller static asset server (index.php)
**Root cause:** Containment check used str_starts_with($realFile, $realBase) without a directory separator boundary, so any resolved path whose string merely begins with the base directory name (e.g. assets-secret) passed.
**Steps:**
1. Ensure user/config/plugin-asset-map.php exists mapping a route prefix to a disk path, e.g. `return ['/myplugin-assets' => 'user/plugins/myplugin/assets'];`
2. Request `/myplugin-assets/../assets-secret/config.php`
3. realpath resolves to `.../assets-secret/config.php`; `str_starts_with(realFile, realBase)` returns true because 'assets-secret' begins with 'assets'
4. Server readfile()s the sibling file and returns its contents
**How found:** Code review of index.php's fast-path asset server: noticed realpath() blocks escaping the filesystem but the containment test was a raw string prefix, then hypothesized sibling dirs sharing the base name prefix would pass.
**Exploitation:** Unauthenticated GET to the mapped route prefix with `../` into any sibling directory whose name extends the base directory name (backup, versioned, legacy dirs), exfiltrating files served directly before Grav boots.
**Insight:** When auditing realpath-based containment, check whether the prefix comparison appends a directory separator; sibling dirs named `base-suffix` (backups, .bak, versioned) are common and pass naive str_starts_with checks.

