
Lago GraphQLのacceptInvite認証バイパスを実証するPoCラボ。未認証の招待承諾を通じてクロス組織のアカウント乗っ取りを可能にする重大な脆弱性を示す。
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · lago-invite-ato
解説記事: https://abraxaslabs.tech/research/lago-invite-ato
Lago v1.53.0 - GetLago
Mutations::Invites::Accept には AuthenticableApiUser がありません。Create/revoke にはあります。Accept にはありません。register_from_invite は招待メールアドレスでユーザーを読み込みます。そのアカウントがすでにメンバーシップを持っている場合、送信されたパスワードは破棄されます。それでも AuthToken.encode(user:) は実行されます。JWT はユーザースコープです。org クレームはありません。クライアントは、そのユーザーがすでに所属している任意の org に対して x-lago-organization を送信します。被害者の org も含まれます。createInvite はすでに招待者にトークンを返します。
cfo@victim を使い捨ての org に招待し、自分で承諾すれば、そのメールアドレスがすでに存在するすべての org において、あなたはその人物になります。メールボックスは一切関与しません。被害者のパスワードは変更されません。
| ID | CVE 未割当 |
| CWE | CWE-287, CWE-306 |
| CVSS | Critical: 9.6 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N |
| 製品 | Lago / lago-api |
| 影響範囲 | v1.53.0 までの email/password による acceptInvite |
| 認証 | org 管理者 (organization:members:create)、Cloud のサインアップで十分 |
| ライセンス | GNU Affero GPL v3.0 |
| ラボ | 127.0.0.1 のみ |
自分が管理する org が必要です。Cloud のサインアップで十分です (LAGO_DISABLE_SIGNUP のデフォルトは false)。被害者のメールアドレスを招待し、ダミーのパスワードで未認証のまま承諾し、その後ヘッダーを Victim Corp に切り替えます。請求書、API キー、Stripe、ウォレット。
アカウントを持たないランダムな訪問者ではありません。パスワードリセットでもありません。ダミーのログインは失敗します。被害者の実際のパスワードは引き続き機能します。Google/Okta による承諾は IdP のメールアドレスをバインドするため、このバグではありません。他の誰のメールアドレスも DB に存在しない単一 org のセルフホストでは、別のマーチャントを取得することはできません。同じデータベースに 2 つの org があり、同じメールアドレスがある場合は可能です。
Stripe のワンタイム請求書 IDOR と同じツリーです。別のバグです。
Accept を読み (AuthenticableApiUser なし)、次に find_or_initialize_by(email:) を読み、次にパスワードをスキップするメンバーシップ存在時の分岐を読み、次に AuthToken.encode(user:) を読みました。次に Google による承諾を読みましたが、これは google_oidc["email"] == invite.email をチェックします。email/password による承諾は、既存ユーザーのパスワードをバインドしません。
これを最初に目にするクライアントは、未使用のアドレスを招待し、それを承諾し、投稿したパスワードで真新しい Lago ユーザーを取得します。それは機能です。証拠となるのは、既存の被害者ユーザー ID が返ってくることです。
すでに記録済みの誤った道: Google/Okta で承諾すること。JWT なしでの createInvite の 401 をすべてと見なすこと (create は認証済み、accept は未認証)。currentUser で攻撃者の org だけを見ること (手がかりはリスト内の Victim Corp)。ダミーのパスワードでログインできること (それはハッシュが上書きされたことを意味する - ラボでは上書きされていないことを検証済み)。リバースシェル。見せかけ。受信箱は不要。
続いて、2つの registerUser 呼び出しで Victim Corp と Attacker Corp を作成します。毎回新しいメールアドレスを使うため、ダーティなボリュームでも user_already_exists の 422 にはなりません。被害者のメールアドレスに対して createInvite を実行します。ダミーパスワードで未認証の acceptInvite を実行します。返されたユーザー id は被害者と一致します。currentUser.organizations には両方が一覧表示されます。ヘッダーを切り替えると Victim Corp が返されます。
cd lab
./run.sh
対象は http://127.0.0.1:13000 のみ。Stripe IDOR は 13001 を使用。UI ポートはなし。
IOC victim_password_unchanged
IOC victim_real_password_still_works
SUCCESS LAGO-INVITE-ATO acceptInvite JWT is existing victim; lists Victim Corp; no inbox
ユーザーが既に存在する場合は、メンバーシップを付与する前に、本人として認証する(またはメールボックス / IdP を証明する)ことを要求してください。未認証の承諾から既存ユーザー用の JWT を発行しないでください。招待 token がケイパビリティである場合は、招待者に返すのをやめてください。
accept.rb · accept_service.rb · users_service.rb · create.rb · object.rb · google_service.rbGNU Affero GPL v3.0。LICENSE を参照してください。ループバックラボ専用です。保証はありません。