Hello there, ('ω')ノ
1. 概要(このラボで何を学ぶか)
このラボでは、
パスワードリセット機能のロジックが壊れている(不完全)ことで、
- リセットトークンを持っていない他人のアカウントのパスワードを変更できてしまう
という脆弱性を学びます。
具体的には:
- 自分のユーザで普通に「パスワードリセット」を体験し、
- Burp で リセット時のリクエスト構造 を観察して、
- トークンが実際には検証されていないことを確認し、
usernameだけcarlosに書き換えてパスワードを上書きする
という流れで、Carlos のアカウント乗っ取り → My account へアクセスしてラボをクリアします。
2. 前提知識
パスワードリセットの正しい流れ
- ユーザ名/メールを入力
- メールに一意のトークン付き URL が届く
- その URL を踏んだ人だけがパスワードを変更できる(トークン検証必須)
トークン検証が抜けていると何が起こるか
→ 「誰でも POST で username を差し替えれば、その人のパスワードを変えられる」Burp での作業
- Proxy > HTTP history でリクエスト調査
- Repeater でリクエストをコピー&改変して再送
3. なぜこの手順で進めるのか(考え方)
まずは 自分のユーザで正常なパスワードリセットの流れを把握 する
→ どんな URL・パラメータ・トークンが使われるかを理解。次に、Burp で パスワード変更の POST リクエスト を観察
→ クエリストリング・ボディの両方にあるtemp-forgot-password-tokenの挙動を確認。トークンを削除してもリセットできてしまうか? を実験
→ ここで「実はトークンが全く検証されていない」という壊れたロジックを確信。最後に、
usernameをcarlosに書き換えて同じ構造のリクエストを送信
→ トークンなしでもターゲットユーザのパスワードが変えられてしまう。
4. 実際の手順(1アクションごとに「なぜ」を説明)
ここから先は、ラボ環境のみで実行してください。
ステップ 1 — 自分のユーザでパスワードリセットの流れを把握
- 画面から 「Forgot your password?」 をクリック
- 自分のユーザ名
wienerを入力し、送信 - 「Email client」ボタンからメールボックスを開き、
パスワードリセットメール内のリンクをクリック - 表示されたページで 新しいパスワード を設定し、送信(どんな値でもよい)
なぜ?
- ここで「正常なフロー」を体験しておくことで、後で Burp でそのリクエストを見た時に
「どれがトークンか」「どこに何が入っているか」が理解しやすくなります。
ステップ 2 — HTTP history でパスワードリセット関連リクエストを確認
- Burp → Proxy > HTTP history を開く
POST /forgot-password?temp-forgot-password-token=...のようなリクエストを探す- URL クエリ:
temp-forgot-password-token=xxxxx - ボディ:
username=wiener&new-password-1=...&new-password-2=...
- URL クエリ:
- このリクエストを Send to Repeater
ポイント
- メール内のリンクに含まれていた「一時トークン」が、
URL のクエリパラメータとして渡されている。
- 同時に、フォームから送られる hidden input 等 にも同じトークンが含まれていることが多い。
ステップ 3 — Repeater でトークンを削除しても動くかをテスト(ロジック崩壊の確認)
Repeater に送った自分のパスワードリセットリクエストを編集します。
- URL 部分から
temp-forgot-password-tokenの値を 空にする- 例:
/forgot-password?temp-forgot-password-token=
- 例:
- リクエストボディ中のトークン値も 空に変更
- もしくは該当パラメータを削除(ラボによるが、まずは値を空にする)
username=wiener、新パスワードは適当な値のままにして送信
期待される結果
- それでもパスワードリセットが成功する(エラーにならない)
ここが重要な「壊れたロジック」
- 本来ならサーバ側は
- URLに含まれたトークン
- そのトークンがDBに保存されているか
- 有効期限内か
を検証するべきです。
- しかし、このラボでは トークン値を無視して username だけで処理している ため、
トークンが空でもパスワードが変わってしまいます。
ステップ 4 — 再度パスワードリセットを行い、攻撃用のリクエストを準備
ラボ手順どおり、もう一度自分のパスワードリセットを行います。
- ブラウザから再度
wienerで「Forgot your password?」を実行 - 新しいリセットリンクを踏んで、新パスワードを適当に設定
- 再び HTTP history から
POST /forgot-password?...を見つけ、
今回のものを Send to Repeater
なぜ再度やる?
- 攻撃時に混乱しないように「最新のフロー」で採取したリクエストをベースにするためです。
- 1回目のトークンを使い回すと、アプリ側が後で無効化しているケースもあり得るため、
ラボ手順に合わせて新鮮なリクエストを利用します。
ステップ 5 — Repeater で username を carlos に変更して送信(攻撃実行)
- Repeater で、再度
- URLの
temp-forgot-password-tokenの値を空にする - ボディのトークン値も空にする
- URLの
username=の値をcarlosに変更new-password-1/new-password-2を、自分がログインに使いたい任意のパスワード に変更- リクエストを送信
期待される結果
- 問題なく「パスワードリセット成功」扱いになる
- つまり、トークン検証なく、username だけでパスワードが書き換えられてしまった ことになる
ステップ 6 — Carlos の新パスワードでログインし、My account へ
- ブラウザに戻る
- ログアウト状態にしてから
carlosと、さきほど自分が設定した新パスワードでログインMy accountページへ遷移- ラボが Solved と表示される
5. どこが問題だったのか(設計の破綻ポイント)
このラボの致命的なポイントは:
- パスワードリセットの POST 時に、トークンが一切検証されていない
- サーバが信じているのは
usernameのみ - つまり「そのユーザとしてパスワードを変える権利があるか?」をチェックしていない
本来必要なチェックは:
temp-forgot-password-tokenがリクエストに含まれているか- それが DB に記録されたトークンと一致しているか
- 対象
usernameとトークンが紐づいているか - 有効期限を過ぎていないか
- 使用済みでないか
これらが 全てスキップ されているのがこのラボの「broken logic」です。
6. 実務での防御策
- トークン中心設計
- パスワード変更時は username をフォームに含めず、
トークンから username を解決する
- パスワード変更時は username をフォームに含めず、
- トークン検証の徹底
- 一意性・有効期限・1回限りの使用・IP / UA 検証など
- エラーハンドリングの慎重設計
- どの条件でエラーを返したかを外部から推測されないようにする
- 監査ログ
- パスワードリセット開始・完了・失敗のログを残し、不審な試行を検出する
7. まとめ(ワンフレーズ)
「パスワードリセットの最終 POST でトークンを見ていない」= username さえ変えれば誰のパスワードでも勝手にリセットできる、という典型的な “broken logic” の事例を体験するラボ。
Best regards, (^^ゞ