Shikata Ga Nai

Private? There is no such things.

Lab: Authentication bypass via OAuth implicit flow

Hello there, ('ω')ノ

概要(ラボの目的と達成条件)

このラボでは、OAuth(ソーシャルログイン)の実装不備を悪用して、他人のアカウントへログインする手法を学びます。 アプリは外部の OAuth サービス(ソーシャルメディア)を使ってログインさせていますが、クライアント側の検証ロジックが甘いため、

  • 自分のソーシャルアカウントで認証したあと、
  • リクエスト中のメールアドレスを「Carlos のメール」に書き換えるだけで、Carlos としてログインできてしまう

という脆弱性があります。

ラボのゴールは:

carlos@carlos-montoya.net としてログインすること

です。


攻撃者の全体的思考(高レベル)

攻撃者としてまず考えるのは:

  1. OAuth ログイン後、クライアント(ブログサイト)は「誰なのか」をどう判断しているか?

    • 多くのサイトは、OAuth プロバイダから受け取る メールアドレスやユーザーID をもとにユーザーを同定しています。
  2. その「ユーザー情報」は、本当に OAuth プロバイダから来たものなのか?それともクライアントが自由に受け取っているだけなのか?

    • もし クライアントの /authenticate へのリクエスト内の値を信用しているだけなら、
    • 攻撃者はそこを 書き換えるだけで別ユーザーとしてログインできる 可能性があります。
  3. どのポイントで「メールアドレス」が登場し、それが改ざん可能か?

    • OAuth 認証が終わってクライアントに戻ってくるまでの HTTP トラフィックを、Burp で一連のフローとして観察します。
    • 特に、クライアントが OAuth サーバから受け取った情報を 自サイトの /authenticate に送るリクエストが重要。

このラボでは、クライアントアプリは以下のような「甘い」設計になっています:

  • OAuth サービスが返したユーザー情報をそのまま信じるだけでなく、
  • /authenticate への POST リクエストの中の email フィールドをそのまま信用している
  • かつその email が アクセストークンと整合しているか検証していない

結果として、アクセストークンは攻撃者のものなのに、メールだけ Carlos にすり替えることで、クライアント側が「これは Carlos だ」と判断してしまいます。


前提(入力、想定される OAuth フローと処理)

このラボにおける簡略化したフローはおおむね次の通りです:

  1. ユーザーがブログサイトで「ソーシャルでログイン」ボタンを押す → ブログサイト(クライアント)が OAuth サーバへリダイレクト → GET /auth?client_id=...&redirect_uri=...&response_type=token などのリクエストが発生

  2. ユーザーが OAuth サーバ上でログイン(wiener:peter) → 承認後、ブログサイトの redirect_uri にリダイレクト

  3. クライアントは OAuth サーバから アクセストークン を取得し、そのトークンを使ってユーザー情報(メールや名前)を取得

  4. クライアントは自アプリ内でログイン処理するために、次のようなリクエストを送ると想定されます:

POST /authenticate HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/json
Cookie: session=...

{
  "email": "wiener@...some-domain...",
  "token": "ACCESS_TOKEN_XXXX"
}

サーバ側(ブログ側)は:

  • token が有効かどうかを何かしらチェックし、
  • email に紐づくユーザーを見つけて、そのユーザーとしてログイン扱いにしている

問題点:

  • email が本当にその token に対応するユーザーのものか を検証していない
  • そのため、攻撃者がこの email を Carlos に変更すると、トークンは攻撃者のものなのに「Carlos のセッション」が発行される

ステップバイステップ(詳細ハンズオン)

ステップ1:自分のアカウントでソーシャルログインする

操作:

  1. Burp でプロキシを有効化したブラウザを開く
  2. ラボのトップページから 「My account」 をクリック
  3. 「ログイン with ソーシャルメディア」的なボタンを押す
  4. 表示された OAuth ログイン画面で wiener / peter でログインする
  5. 承認を済ませると、ブログサイトに戻り、自分(wiener)としてログインされた状態になる

なぜこの操作か:

  • 実際の OAuth フローが どの URL で、どんなパラメータをやりとりしているか を確認するため。
  • 特に、クライアントに戻ってきたあとで送信される /authenticate のリクエストが重要なので、それを発生させる必要がある。

ステップ2:Burp で OAuth フローを確認する

操作:

  1. Burp の Proxy → HTTP history を開き、ログイン時のリクエストを追っていく
  2. GET /auth?client_id=... から始まるリクエストを探す → これが OAuth サーバへの認可リクエスト
  3. その後に続く、リダイレクトや /callback 的な URL を眺めていく
  4. 最終的に、ブログサイト側が自分自身に送っている POST /authenticate のリクエストを探す

なぜこの操作か:

  • 攻撃ポイントは クライアント側の /authenticate なので、そのリクエストの中身(ボディ)を知る必要がある。
  • OAuth プロバイダの仕組みそのものを壊すのではなく、クライアントアプリの実装ミス を突くのがこのラボの本質。

ステップ3:POST /authenticate の中身を分析

見つけた POST /authenticate リクエストをクリックし、リクエストボディを確認します。 典型的には以下のような内容になっています(実際のパラメータ名はラボによって多少前後します):

POST /authenticate HTTP/1.1
Host: YOUR-LAB-ID.web-security-academy.net
Content-Type: application/json;charset=UTF-8
Cookie: session=...

{
  "email": "wiener@social.example.net",
  "token": "eyJhbGciOi..."  // アクセストークン
}

あるいは URL エンコード形式の場合もあります:

email=wiener%40social.example.net&token=eyJhbGciOi...

注目すべき点:

  • email フィールド:この値をクライアントは「ログインするユーザー」を決める鍵として使っている
  • token フィールド:OAuth プロバイダから取得したアクセス・トークンだが、このトークンと email の整合性が検証されていない

ステップ4:/authenticate を Repeater に送り、メールを Carlos に偽装する

操作:

  1. Proxy の HTTP history で POST /authenticate を右クリック → Send to Repeater を選択
  2. Repeater タブに移動し、先ほどのリクエストが表示されていることを確認
  3. リクエストボディの email を、以下のように書き換える:
"email": "carlos@carlos-montoya.net"

または URL エンコードなら:

email=carlos%40carlos-montoya.net&token=...
  1. そのまま Send をクリックして送信

なぜこの操作か:

  • 攻撃者は、自分(wiener)として取得したトークンを使いまわしつつ
  • メールアドレスだけを Carlos に偽装することで、
  • クライアントアプリに「これは Carlos のログインだ」と思い込ませる。

ここでエラーが返らず、通常どおりセッションが更新されることが重要な観察ポイントです。


ステップ5:ブラウザでこのリクエストを再現して Carlos としてログインする

Repeater でリクエストを送るだけでは、ブラウザ側のクッキーやセッションが切り替わりません。 そこで Burp の「Request in browser」機能を使って、ブラウザ側で同じリクエストを発生させる必要があります。

操作:

  1. Repeater でメールを carlos@carlos-montoya.net に変えた状態の POST /authenticate を表示している
  2. そのリクエスト上で右クリック → Request in browser → In original session を選択
  3. ダイアログボックスに表示される URL をコピー
  4. Burp のブラウザに戻り、その URL をアドレスバーに貼り付けてアクセス

これにより、ブラウザは元のセッション Cookie を使いながら、 Carlos のメールアドレスを使った /authenticate を実行します。

その結果:

  • ブログサイトは「これは Carlos のログインだ」と誤認し、
  • ブラウザ上では Carlos としてログインした状態になります。

My account ページなどを開いて Carlos であることを確認すると、ラボは Solved になります。


Burp 操作(具体的な画面操作)

1. OAuth フローのトレース

  • Proxy → HTTP history を開く
  • GET /auth?client_id=... から始まり、リダイレクトをたどり、POST /authenticate を見つける

2. Repeater 送信

  • POST /authenticate を右クリック → Send to Repeater
  • JSON / フォームボディ内の email を Carlos に書き換えSend

3. Request in browser

  • 書き換え済みの POST /authenticate を右クリック → Request in browser → In original session
  • 表示された一時 URL をブラウザに貼り付けてアクセス
  • その後、/my-account などに移動して Carlos ログインを確認

レスポンス確認方法(成功のサイン)

成功しているかどうかを確認するポイントは:

  1. /authenticate のレスポンスが エラーになっていないこと

    • 例: 200 OK、または 302 で /my-account にリダイレクトなど
    • 「invalid token」「email mismatch」のようなエラーが出ない
  2. ブラウザで My account 画面を確認すると、

    • ユーザー名やメールアドレスが Carlos のものに変わっている
  3. ラボのメイン画面上部に

    • 「Congratulations, you solved the lab!」 と表示されている

トラブルシュート(失敗パターンと対策)

症状 原因 対策
/authenticate がエラー(401/403) トークンが古くなっている / セッション切れ もう一度最初から OAuth ログインし直し、新しい /authenticate をキャプチャして使う
Request in browser を使っても何も変わらない 「In original session」以外を選んだ / Cookie が合っていない 必ず In original session を選択し、同じブラウザからアクセスする
Carlos にならない email の書き換えが反映されていない / 位置が違う リクエストボディ内の すべての email フィールドcarlos@carlos-montoya.net に変更
Repeater では OK だが、ブラウザで再現できない 一時 URL をコピーし損ねた 再度「Request in browser」を実行して、新しい URL を正確にコピー

防御(開発者向け対策)

この種の脆弱性は、OAuth プロバイダではなく、クライアント側の実装の問題です。 開発者は以下の点に注意する必要があります。

  1. クライアント側で「信頼するのはアクセストークンとユーザー情報 APIだけ」にする

    • /authenticate のような自前エンドポイントで、クライアントから任意の email を受け取らないこと。
    • ユーザー情報は必ず OAuth プロバイダの /userinfo などから直接取得し、クライアント送信の email は無視する。
  2. アクセストークンとユーザー情報の整合性を必ず検証する

    • 例えば OpenID Connect の ID Token を使う場合は、その署名や sub クレームを検証し、 トークンと結びついたユーザー以外としてログインしない。
  3. クライアント側での「便利な中継パラメータ」を信用しない

    • 開発の都合で「とりあえず email も一緒に POST で送ろう」としがちだが、 それをそのままログイン判断に使うのは危険。
  4. OAuth 実装ベストプラクティスに従う

    • ライブラリやフレームワークの標準的なフロー(Authorization Code Flow + PKCE など)を利用し、 自前で危険な「なんちゃって実装」をしない。

Best regards, (^^ゞ