CSRF(Cross-Site Request Forgery)は、攻撃者が被害者のブラウザを利用して、被害者の意図しないリクエストを送信させる攻撃です。OAuthのフローにおいては、コールバックURLへのリダイレクトが特に狙われます。
攻撃者のアカウントを被害者に紐付ける攻撃(アカウント乗っ取り)
攻撃者が 正規のOAuthフローを開始し、認可サーバーでログインする
コールバックURLにリダイレクトされる直前で、認可コード付きのコールバックURLを記録して中断する
https://app.example.com/callback?code=ATTACKERS_CODEこのURLを被害者に踏ませる(メール、SNS、不正なWebページなど)
被害者のブラウザがコールバックURLにアクセスし、 アプリケーションが攻撃者の認可コードを使ってトークンを取得
被害者のセッションに攻撃者のアカウントが紐付けられる。攻撃者は自分のアカウントで被害者のデータにアクセスできる。
stateパラメータは、認可リクエストとコールバックを紐付けるためのランダムな値です。これにより、コールバックが自分が開始したフローの結果であることを確認できます。
クライアントがランダムな state 値を生成し、セッションに保存する
認可リクエストに state パラメータを含めて送信する
認可サーバーは state をそのままコールバックURLに付与して返す
クライアントは、返ってきた state とセッションに保存した値を比較し、一致しなければリクエストを拒否する
state値の生成(推奨)
// 暗号的に安全なランダム値を生成
const state = crypto.randomUUID();
// セッションに保存
session.oauthState = state;
// 認可リクエストに含める
const authUrl = new URL("https://auth.example.com/authorize");
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("client_id", "my-app");
authUrl.searchParams.set("redirect_uri", "https://app.example.com/callback");
authUrl.searchParams.set("state", state);コールバックでの検証
// コールバックで受け取ったstateを検証
function handleCallback(callbackParams, session) {
const returnedState = callbackParams.get("state");
const savedState = session.oauthState;
// stateが一致しない場合はCSRF攻撃の可能性
if (!returnedState || returnedState !== savedState) {
throw new Error("state mismatch - possible CSRF attack");
}
// 使用済みのstateを削除(再利用防止)
delete session.oauthState;
// 認可コードの処理を続ける...
}- 1.アカウント紐付け攻撃
攻撃者のソーシャルアカウントが被害者のアプリアカウントに紐付けられ、 攻撃者がそのアカウントにアクセスできるようになる。
- 2.セッション固定攻撃
攻撃者が自分のセッションを被害者に使わせることで、 被害者の操作を攻撃者のアカウントで行わせる。
- 3.ログインCSRF
被害者が知らないうちに攻撃者のアカウントにログインさせられ、 被害者の行動が攻撃者のアカウントに記録される。
- -暗号的に安全なランダム値を使う(UUIDv4 や crypto.getRandomValues)
- -state値は最低128ビットのエントロピーを持つ
- -stateは一度使用したら破棄する(再利用しない)
- -stateに有効期限を設定する(長時間放置されたフローは無効にする)
- -SPAの場合、sessionStorageに保存する(タブごとに独立)
次のステップ
次は PKCEの仕組み で、認可コード横取り攻撃に対する防御策を学びましょう。