【入門編】 OAuth 2.0の認可コード横取り攻撃(Authorization Code Interception) – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

こんにちは!セキュリティの世界へようこそ。
初めてWebアプリやAPIの認証まわりを触る方にとって、「OAuth 2.0」とか「認可コード」といった言葉は、ちょっと呪文のように聞こえて難しく感じてしまいますよね。でも、安心してください。一歩ずつ、身近な例えから紐解いていけば、誰でもしっかりと理解できるようになりますよ。

今回は、OAuth 2.0の仕組みの裏に潜むちょっと怖い落とし穴、「認可コード横取り攻撃(Authorization Code Interception)」について、泥棒と合鍵の防犯に例えながら優しく解説していきます。実務で使える対策コードも用意しましたので、一緒に学んでいきましょう!

—

1. 例え話でスッキリ理解!OAuth 2.0と「合鍵」の仕組み

まずは、OAuth 2.0(オーオーアス ニーテンゼロ)が普段どんなことをしているのか、おうちのセキュリティに例えてみますね。

あなたが「写真共有アプリ(スマホアプリ)」を使っていて、そこに「クラウドストレージ(別のサービス)」の写真を表示させたいとします。
ここで普通なら、「クラウドストレージのパスワード」を写真アプリに教えちゃいそうになりますよね。でも、これだと写真アプリを作る会社が悪人だったら、あなたの他のデータまで全部見られてしまって危険です。

そこで登場するのが、OAuth 2.0という仕組みです。これは、いわば「我が家の玄関の鍵は渡さず、『写真を見るための専用の一時パス(期間限定の合鍵)』だけを渡す仕組み」になります。

泥棒が狙うのは「合鍵を受け取る瞬間」

この仕組みを悪用しようとするサイバー攻撃者(泥棒)は、まさにその「合鍵(認可コード)」が家主(ユーザー)からアプリへと手渡される瞬間を狙っています。

お父さん(ユーザー)が、「はい、この合鍵を使ってね」とアプリに鍵を渡そうとしたとき、もし不審な偽の郵便受け(不正なリダイレクト先)があったらどうでしょう?
うっかりその偽の郵便受けに合鍵を入れてしまうと、泥棒にその鍵がそっくりそのまま盗まれてしまいますよね。これが、今回解説する「認可コード横取り攻撃」の正体です。

—

2. 認可コード横取り攻撃のメカニズム

では、もう少し技術寄りの言葉で、この攻撃がどうやって起こるのかを覗いてみましょう。

スマートフォンアプリなどがOAuth 2.0を使ってログインする際、大まかに次のようなステップを踏みます。

1. 認可リクエスト: アプリがユーザーをブラウザ経由で認証サーバー(GoogleやLINEなど)に飛ばす。
2. 認可コードの発行: ユーザーがログインして「許可する」と押すと、認証サーバーはアプリへ向かって「認可コード」という小さなチケットを発行する。
3. リダイレクト: 認証コードを抱えたブラウザが、アプリがあらかじめ指定した宛先(リダイレクトURI)に戻ってくる。
4. トークン交換: アプリはその認可コードを裏側で認証サーバーに渡し、「本物のアクセストークン(真の合鍵)」と交換する。

どこに罠があるの?

問題はステップ3の「リダイレクト」です。スマホアプリの場合、アプリ専用の独自のURLスキーム(例: myphotoapp://callback など)を使って、ブラウザからアプリへと画面を戻します。

もし、このURLスキームを悪意ある別のアプリがスマホ内で勝手に「私のアプリもこの合鍵を受け取る住所として登録しちゃおう!」と乗っ取っていたらどうなるでしょうか?
ユーザーが「許可する」を押した瞬間、本来のアプリではなく、攻撃者のアプリに認可コードがピュッと飛んでいってしまうのです。

攻撃者はそのコードを手に入れれば、あとは自分のものとしてアクセストークンに変換し、ユーザーになりすましてアカウントを乗っ取ることができてしまいます。怖いですね……!

—

3. 救世主登場!「PKCE(ピクシー)」という最強の防犯ロック

「じゃあスマホアプリでのログインなんて怖くて作れないよ!」と思ったそこのあなた、安心してください。この問題をスパッと解決する現代のスタンダードな防御策が用意されています。

それがPKCE(Proof Key for Code Exchange:ピーケーシーイー、通称ピクシー)と呼ばれる仕組みです。

鍵の受渡しの秘密の合言葉

PKCEの考え方はとてもシンプルです。先ほどの例えで言うと、合鍵を渡すときに「2人だけの秘密の合言葉(コード・ベリファイアー)」をあらかじめ決めておく方法です。

1. アプリがリクエストを送るとき、ランダムな文字列(合言葉)を作り、その「ハッシュ値(暗号化して形を変えたもの)」だけを認証サーバーにこっそり伝えます(「この合言葉を知っている人だけに鍵を渡してね」と約束します)。
2. 認証コードが横取りされて、攻撃者がそれを手に入れたとします。
3. しかし、攻撃者は肝心の「元の合言葉(コード・ベリファイアー)」を知りません。
4. 認証サーバーは、「合言葉の証明ができないから、このコードは渡さないよ!」と突っ返すことができます。

これで、仮に途中で認可コードが盗聴されたとしても、中身を使わせないようにガチガチにガードできるというわけです。

—

4. 実務で実装する際のコード例と設定ポイント

それでは、ここからは実際の開発現場でどのようにPKCEを組み込むのか、具体的な設定やコードを見ていきましょう。一歩ずつ実装すれば難しくありません!

① リクエスト送信時のパラメータ設定(フロントエンド / モバイルアプリ)

OAuth 2.0の認可リクエストを送る際、通常のパラメータに加えてPKCE用のパラメータを付与します。以下はJavaScript(WebやReact Native等)のイメージコードです。

// ランダムな文字列(コード・ベリファイアー)を生成する関数
function generateCodeVerifier() {
    const array = new Uint8Array(32);
    window.crypto.getRandomValues(array);
    // URLセーフなBase64エンコードに変換
    return btoa(String.fromCharCode.apply(null, array))
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=+$/, '');
}

// ベリファイアーからチャレンジ(ハッシュ値)を生成する関数(SHA-256)
async function generateCodeChallenge(verifier) {
    const encoder = new TextEncoder();
    const data = encoder.encode(verifier);
    const digest = await window.crypto.subtle.digest('SHA-256', data);
    
    return btoa(String.fromCharCode.apply(null, new Uint8Array(digest)))
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=+$/, '');
}

// 認証フローの開始時に実行する処理
async function startOAuthFlow() {
    const codeVerifier = generateCodeVerifier();
    // 後でトークン交換の時に使うので、安全なストレージに一時保存しておく
    sessionStorage.setItem('code_verifier', codeVerifier);

    const codeChallenge = await generateCodeChallenge(codeVerifier);

    // 認証サーバーへのリクエストURLを組み立てる
    const authEndpoint = 'https://auth.example.com/oauth/authorize';
    const clientId = 'your_client_id_12345';
    const redirectUri = 'myphotoapp://callback'; // アプリのカスタムURLスキーム

    const authUrl = `${authEndpoint}?response_type=code` +
                    `&client_id=${encodeURIComponent(clientId)}` +
                    `&redirect_uri=${encodeURIComponent(redirectUri)}` +
                    `&code_challenge=${encodeURIComponent(codeChallenge)}` +
                    `&code_challenge_method=SHA-256`; // PKCEの方式を指定

    // ユーザーを認証画面へリダイレクト
    window.location.href = authUrl;
}
  • ポイント: code_challenge と code_challenge_method=SHA-256 を必ず含めるようにしましょう。これによってサーバー側に「PKCEを使うよ」と宣言します。

—

② トークン交換時のパラメータ設定(バックエンド / アプリ内処理)

ユーザーが無事にログインを済ませ、アプリへ「認可コード」が戻ってきたら、今度はバックエンド(またはアプリの通信処理)でアクセストークンに交換します。このとき、最初に作った生の状態の合言葉(code_verifier)を一緒に送信するのがポイントです。

以下はPHPを使ったバックエンドでのトークン交換のサンプルコードです。

<?php
// リダイレクト先で受け取った認可コードと、事前に保存しておいたベリファイアーを取得
$authorizationCode = $_GET['code'] ?? '';
$codeVerifier = $_SESSION['code_verifier'] ?? ''; // 保存しておいた生の合言葉

if (empty($authorizationCode) || empty($codeVerifier)) {
    die('必要なパラメーターが不足しています。');
}

// 認証サーバーのトークンエンドポイント
$tokenEndpoint = 'https://auth.example.com/oauth/token';

$postData = [
    'grant_type'    => 'authorization_code',
    'client_id'     => 'your_client_id_12345',
    'code'          => $authorizationCode,
    'redirect_uri'  => 'myphotoapp://callback',
    'code_verifier' => $codeVerifier // ここで生の合言葉を送信し、サーバー側で検証してもらう
];

// cURLを使って認証サーバーへPOSTリクエストを送信
$ch = curl_init($tokenEndpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));

$response = curl_exec($ch);
curl_close($ch);

$tokenData = json_decode($response, true);

if (isset($tokenData['access_token'])) {
    // 無事にアクセストークンの取得に成功!
    $accessToken = $tokenData['access_token'];
    echo "アクセストークンの取得に成功しました!安全にAPIを利用できます。";
} else {
    // 認証エラーや検証失敗
    echo "トークンの取得に失敗しました。不正なリクエストの可能性があります。";
}
  • ポイント: サーバー側は、最初に受け取った code_challenge と、今回送られてきた code_verifier をハッシュ化して一致するかをチェックします。ここで一致して初めてアクセストークンが発行されるため、万が一認可コードが途中で盗まれても攻撃者は先に進めなくなります。

—

5. まとめ:安全なアプリケーション開発のために

今回はOAuth 2.0の認可コード横取り攻撃と、それを防ぐための強力な武器「PKCE」について解説しました。

  • 認可コードは、スマホのURLスキームなどを通じて悪意あるアプリに横取りされるリスクがある。
  • 特にネイティブアプリやSPA(シングルページアプリケーション)では、PKCEの導入が事実上の必須要件となっている。
  • 実装の際は code_challenge と code_verifier を正しくペアで扱い、サーバー側できっちり検証させる。

セキュリティの仕組みは最初は複雑に感じるかもしれませんが、「誰がどこで鍵を持っているか」を意識するだけで、グッと理解しやすくなります。ぜひご自身の開発するアプリやインフラでも、PKCEが正しく有効化されているか確認してみてくださいね。一歩ずつ、安全で強固なシステムを作っていきましょう!

コメント

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