こんにちは!Webアプリケーションの裏側を支えるログインの仕組み、日々のお仕事でお疲れ様です。「OAuth 2.0」とか「OIDC(OpenID Connect)」といった言葉、最近よく耳にするけれど、なんだか難しそうだな…と感じていませんか?
大丈夫です!今回は、新人のIT担当者やセキュリティに初めて触れる開発者の方向けに、このOAuthやOIDCの「ちょっとした設定ミス」が引き起こす恐ろしい罠と、その対策を身近な例えを交えながら、一歩ずつ優しく紐解いていきたいと思います。
—
家の鍵に例えて理解する「OAuth 2.0」と「認可コードフロー」
まずは、OAuth 2.0やOIDCがどんな仕組みなのか、私たちの日常にある「合鍵」に例えて考えてみましょう。
皆さんがお友達の家に遊びに行くときを想像してください。あなたはその家の「完全な持ち主(オーナー)」ではありませんよね。でも、お友達から「今度遊びに来るとき、留守番のついでに郵便受けから手紙を取っておいてほしいんだ」と頼まれました。
ここで、お友達があなたに「家全体のマスターキー(パスワード)」を渡してしまうでしょうか? 「いやいや、マスターキーを渡すのは怖すぎるよ!」と思いますよね。
そこで登場するのが「一時的な合鍵(アクセストークン)」や、「特定の用事だけをお願いする委任状(OAuthの仕組み)」です。
- オーナー(あなた=リソースオーナー): ユーザー本人
- お友達の家(GoogleやLINEなどの認証基盤=認可サーバー): アカウント情報を管理している場所
- 郵便受けを取るお手伝いアプリ(サードパーティ製アプリ): あなたが使いたい便利ツール
この「お手伝いアプリ」に、Googleなどのアカウントを使って安全にログインしてもらうための代表的な手順が「認可コードフロー」と呼ばれる仕組みになります。
—
狙われる盲点!「リダイレクトURIの検証不備」とは?
さて、ここからが本題です。攻撃者がどこを狙うのか、先ほどの「合鍵と郵便受け」の例えに戻って考えてみましょう。
お手伝いアプリが、お友達の家(認可サーバー)から「一時的な合鍵(認可コード)」を受け取るとき、こんなやり取りが発生します。
1. あなた: 「このアプリに郵便受けを取る権限を許可します!」
2. お友達の家: 「分かった!じゃあ、この『合鍵交換チケット(認可コード)』を、あなたのスマホのhttps://app.com/callbackという住所に届けるね」
ここで問題になるのが、この「届ける先の住所(リダイレクトURI)」の確認が甘い場合です。
もし、お友達の家が「あ、住所の確認? 大体合っていればいっか!」と適当に荷物を送り出してしまったらどうなるでしょうか?
悪意ある泥棒(攻撃者)が、あなたのスマホの裏でこっそり待ち構えていて、こう叫んだらどうでしょう。
「あ、その『合鍵交換チケット』、私の偽の住所(https://attacker.com/steal)宛てに送ってください!」
認可サーバーがこの嘘を見抜けず、攻撃者の指定した住所にチケットを送ってしまうと、攻撃者はあなたの代わりにそのチケットを受け取り、あなたのアカウントを乗っ取ってしまいます。これが「リダイレクトURIの検証不備」という脆弱性です。
攻撃の裏側を覗いてみよう(パラメーターの仕組み)
実際のWebブラウザでは、次のようなURL(リクエスト)のやり取りが行われています。
# ユーザーがアプリから認証を求められたときに、認可サーバーへ送るリクエストの例
GET /oauth/authorize?
response_type=code&
client_id=your_cool_app_123&
redirect_uri=https://app.com/callback& # ← ここを攻撃者のURLにすり替えるのが狙い!
scope=read_profile
HTTP/1.1
Host: auth.example.com
開発段階で「テストだから、リダイレクト先のURLはワイルドカード(https://*.app.com/*)でいいか!」と雑に設定してしまったり、完全一致のチェックをサボったりすると、この罠に足元をすくわれることになります。
—
スコープの過剰付与:何でも屋の「合鍵」を作っていませんか?
もう一つのよくある落とし穴が「スコープの過剰付与」です。
「スコープ」とは、先ほどの例えでいう「お使いの範囲」のことです。「郵便受けから手紙を取るだけ」の用事なのに、「家の中の金庫を開ける権利」や「家族の通帳を見る権利」まで、すべての権限が詰まったマスターキーのような合鍵をアプリに渡してしまっていないでしょうか?
もし、あなたが便利そうだからと軽い気持ちで「全権限(all や admin スコープ)」を許可したアプリをインストールし、そのアプリが万が一ハッキングされたら……。アプリの運営会社ではなく、あなたの大切な個人情報やプライベートなデータが丸ごと盗まれてしまいます。
「最小権限の原則(必要最低限の権限だけを与える)」は、現実世界の防犯と同じく、Webの世界でも鉄則です。
—
一歩ずつ対策を学んでいきましょう!
「うわ、なんだか怖くなってきたな…」と思いましたか?大丈夫です。しっかりと正しい設定を行えば、これらのリスクは確実に防ぐことができます。今日から実践できる具体的な対策を見ていきましょう。
1. リダイレクトURIは「完全一致」で厳格に検証する
コードを書く際、またはOAuthプロバイダー(Auth0やCognito、自社製サーバーなど)を設定する際は、リダイレクトURIの部分文字列やワイルドカードによるあいまいな判定を絶対に避けましょう。
/* 良い設定例:完全なURLを指定し、部分一致を許可しない */
{
"client_id": "your_cool_app_123",
"allowed_redirect_uris": [
"https://app.com/auth/callback" // 必ずプロトコル、ドメイン、パスまで完全に一致させる
]
}
2. ステートパラメータ(state)を必ず実装してCSRFを防ぐ
リダイレクトの際に、偽装されたリクエスト(CSRF攻撃)を防ぐため、ランダムな文字列をstateパラメータとして挟むのが鉄則です。
<?php
// PHPでの安全なstate生成とセッション保存の例
session_start();
// 1. 推測困難なランダムなトークンを生成する
$state = bin2hex(random_bytes(16));
$_SESSION['oauth_state'] = $state;
// 2. 認可サーバーへリダイレクトするURLを組み立てる
$auth_url = "https://auth.example.com/oauth/authorize?" . http_build_query([
'response_type' => 'code',
'client_id' => 'your_cool_app_123',
'redirect_uri' => 'https://app.com/auth/callback',
'state' => $state, // ← ここに必ず仕込む!
'scope' => 'read:profile'
]);
// 3. 認証画面へ誘導
header("Location: " . $auth_url);
exit;
?>
そして、認可サーバーからユーザーが戻ってきた(callbackを受け取った)際、送信されてきたstateの値と、セッションに保存しておいたstateの値が完全に一致するか必ずサーバー側で突合してください。ここが一致しない場合、不正なリクエストとして処理を即座に中断します。
3. PKCE(Proof Key for Code Exchange)を導入する
「ネイティブアプリ」や「シングルページアプリケーション(SPA)」など、ソースコードがユーザーに見えてしまう環境では、従来の認可コードフローだけではセキュリティ的に少し心もとない場合があります。そこで現在は、クライアントシークレットを安全に隠せない環境でも安全に認証を行えるPKCE(ピーシーと読みます)の導入が業界標準(デファクトスタンダード)になっています。
一歩ずつ、まずはリダイレクトURIの厳格なチェックと、不要なスコープをもらわない・与えない設計から見直してみましょう。セキュリティの強固な扉は、日々の丁寧な設定の積み重ねから作られます。一緒に安全なWebアプリケーションを作っていきましょうね!
コメント