【実務・中級編】OAuth 2.0における認可コード横取り攻撃とPKCEの必須化 – アプリケーションセキュリティ & 安全な開発防御ガイド

OAuth 2.0の「認可コード横取り」はなぜ終わらないのか?――PKCE実装を「推奨」ではなく「義務」とすべき理由

現場でコードをレビューしていると、いまだに「認可コードグラント(Authorization Code Grant)」において、PKCE(Proof Key for Code Exchange)を省略しているケースに出くわすことがある。

「うちはスマホアプリじゃないから関係ない」「サーバーサイドで完結しているから安全だ」――。もし君がそう思っているなら、今日でその考えは捨ててほしい。PKCEはもはや、モバイル特有のパッチではない。Webアプリを含めたOAuth 2.0実装の「必須要件」だ。

今回は、なぜ認可コードが盗まれるのか、そして我々がどう実装すべきかについて、現場の泥臭い視点から解説する。

—

1. 認可コード横取り攻撃(Authorization Code Interception)のリアル

攻撃者が狙うのは、ブラウザと認可サーバーの間で行われる「リダイレクト」の隙間だ。

攻撃シナリオ

1. トリガー: 攻撃者が仕掛けた悪意のあるアプリや、ブラウザの拡張機能、あるいはOSのカスタムURLスキームの登録競合を悪用する。
2. 横取り: ユーザーが認可サーバーでログイン後、サーバーは redirect_uri へ認可コードを投げる。この時、もしクライアント側がPKCEで保護されていないと、攻撃者はこのリダイレクトを横取りし、認可コードを吸い上げることができる。
3. 交換: 攻撃者はそのコードを自身の認可サーバーへ持ち込み、client_secret が不要な(あるいは漏洩した)状況であれば、そのままアクセストークンを取得できてしまう。

特に、client_secret を隠しきれないシングルページアプリケーション(SPA)や、カスタムURIスキームを利用するモバイルアプリでは、この攻撃は致命的だ。

—

2. PKCEが解決する「信頼のギャップ」

PKCEの仕組みは非常にエレガントだ。一言で言えば、「最初に自分が投げた『ナゾナゾ』の答えを、後で本人であることを証明するために提出させる」というもの。

  • Code Verifier: クライアントが生成するランダムな文字列(秘密の鍵)。
  • Code Challenge: VerifierをSHA-256でハッシュ化したもの。

最初の認可リクエストで code_challenge を送り、認可コードとアクセストークンを交換するリクエストで code_verifier を送る。認可サーバーは「後から送られてきたverifierをハッシュ化したら、最初に受け取ったchallengeと一致するか?」を確認する。攻撃者が途中でコードを横取りしても、元のverifierを知らないため、トークン交換は成立しない。

—

3. 実装のベストプラクティス:Node.js (Express) でのPKCEフロー

現場でそのまま使えるよう、PKCEの肝となるロジックをNode.jsで実装した例を示す。

const crypto = require(‘crypto’);

// 1. コードベリファイアの生成(高エントロピーな乱数)
function generateCodeVerifier() {
return crypto.randomBytes(32).toString(‘base64url’);
}

// 2. コードチャレンジの生成(SHA-256でハッシュ化)
function generateCodeChallenge(verifier) {
return crypto.createHash(‘sha256’).update(verifier).digest(‘base64url’);
}

// 認可リクエスト時のURL構築例
const verifier = generateCodeVerifier();
const challenge = generateCodeChallenge(verifier);

// この ‘verifier’ は、アクセストークン取得時までセッションまたはローカルストレージに保持する
console.log(認可リクエストパラメータに追加: &code_challenge=${challenge}&code_challenge_method=S256);

アクセストークン取得時のコード交換(バックエンド側):

// POST /token リクエスト時に以下を含める
const params = new URLSearchParams({
grant_type: ‘authorization_code’,
code: receivedCode, // 横取りされたコード
redirect_uri: REDIRECT_URI,
client_id: CLIENT_ID,
code_verifier: storedVerifier // 最初に生成した本物のverifierを送信
});

// 認可サーバー側でチェックされるため、攻撃者はここで弾かれる

—

4. 現場のインフラ担当へ:WAFと設計で守るべき「防波堤」

コード実装だけでなく、インフラ側での防御も重要だ。特に以下のポイントをチェックしてほしい。

  • Redirect URIの完全一致:

認可サーバーの設定で、redirect_uri は正規表現やワイルドカードではなく「完全一致」のみを許可すること。サブドメインの乗っ取り一つで、認可コードが攻撃者のドメインへ送信されるリスクを排除する。

  • Referrer Policyの厳格化:

HTTPヘッダーで Referrer-Policy: no-referrer を設定し、リダイレクト時に認可コードが含まれたURLが外部のサードパーティスクリプトへ漏れるのを防ぐ。

  • WAFのルール設定:

認可サーバーへの不審なリクエスト(短時間に大量のコード交換要求など)を検知し、IP単位でブロックするルールをAWS WAFやCloudflareで設定しておくことが肝要だ。

—

最後に:セキュリティは「諦め」の積み重ねではない

「PKCEを実装するのは面倒だ」「仕様が複雑になる」とぼやいている間に、攻撃者はツールを自動化し、数秒で脆弱なエンドポイントをスキャンしている。

私が何件ものインシデントを見て学んだのは、「複雑さを理由にセキュリティを省略した箇所が、必ず一番最初に狙われる」という冷徹な事実だ。PKCEの導入は、OAuth 2.0を運用するエンジニアにとっての「礼儀」であり、最低限の「防弾チョッキ」である。

君のシステムが信頼されるかどうかは、こうした地味で堅実な実装の積み重ねにかかっている。次回のリリースでは、必ず「PKCE必須化」がチェックリストの筆頭にあることを期待している。

コメント

タイトルとURLをコピーしました