Hello there, ('ω')ノ
非標準データ構造に潜むStored XSS
0. 概要(TL;DR)
セッションクッキーが <username>:<token> という非標準の構造を持ち、その username サブフィールドが Stored XSS のシンクになっている。username はサーバ側のアクセスログ/分析(admin閲覧用)に記録され、admin がそれを閲覧した際にスクリプトが発火する。
通常はBurp Scannerの「Scan selected insertion point」でこの非標準構造内の脆弱性を発見する想定のラボだが、本診断では手動で構造を解析し、curl/Pythonで自律的に攻略した。整合性チェック(Integrity violation detected)でリクエスト自体は500になるものの、改ざんしたusernameはログに記録され続ける点を突き、admin のブラウザで GET /admin/delete?username=carlos を実行させて carlos を削除した。
- 脆弱箇所: セッションクッキー
session=<username>:<token>の username 部分 - ペイロード(username部):
'"><svg/onload=fetch(\/admin/delete?username=carlos`)>` - 結果: admin閲覧時にXSS発火 → carlos削除 → Solved
1. 対象と前提
| 項目 | 値 |
|---|---|
| 対象 | https://0a19002a04f9b74780e6214c00a1008b.web-security-academy.net/ |
| 自アカウント | wiener:peter |
| 目標 | Stored XSS で admin セッションを悪用し carlos を削除する |
| 想定ツール | Burp Scanner(本診断では未使用=手動で代替) |
2. 非標準データ構造の解析
2.1 クッキー構造
ログイン後のクッキー(URLデコード後):
session = wiener:qYsn0vrs6XGxH3fcHqqiEwta5nPMo3Pf
└ username ┘ └────────── token ──────────┘
- base64ではなく平文の
username:token。 - token はログイン毎に変化(
qYsn...→JMG2...→leWY...)。値が毎回変わることから、token は username 由来の決定的MAC ではなく、独立したセッションIDと推定(saltつきHMACの可能性も完全には排除できないため断定はしない)。
2.2 整合性チェックの存在
username 部分を改ざんすると全エンドポイントで 500 を返す:
| クッキー | 結果 |
|---|---|
wiener:<token>(正規) |
200 |
wiener':<token>(username改変) |
500 Integrity violation detected |
ZZMARK:<token> |
500 Integrity violation detected |
wiener:<token>ZZMARK(token改変) |
302(セッション無効→ログアウト) |
→ 挙動差(token改変=302/ログアウト、username改変=500/Integrity violation)から、サーバは token から引いたセッションの username と、クッキー平文の username の一致を検証していると推定(実装が文字列比較かMAC検証かは外形からは不明)。いずれにせよ cookie だけで username を自由に変えると 500 になる。
2.3 Stored XSS のシンク(admin専用)
改ざん username を送っても、wiener が閲覧可能な /・/my-account・/analytics のいずれにも反映されない(§検証で marker 不検出)。/admin は Admin interface only available if logged in as an administrator。
→ シンクは admin が閲覧する分析ダッシュボード等にクッキー由来の username が記録・描画されるもの、と消去法で推定(攻撃者側からは直接観測できず、後述のSolved到達という結果から逆算した推定)。これが Burp Scanner が「Stored XSS」と判定する所以であり、非標準構造ゆえ手動発見が難しい(=ラボの主旨)。なお「Stored」判定も、別主体(admin)が後刻に閲覧して発火した結果からの推定で、消去法による分類である点を付記する。
3. 攻撃
3.1 着眼点:整合性違反でも username は記録される
整合性チェックはリクエスト処理を 500 で止めるが、アクセスログ/分析への username 記録はそれより前段(信頼境界の手前)で行われていると推定される(直接観測はできておらず、<XSS>:<token> 送信→500 受信→数秒後に carlos 削除・Solved という結果からの逆算)。すなわち攻撃者は 500 を受け取るだけだが、XSSペイロードはadmin閲覧用のログに保存され、後刻 admin の閲覧で発火する。
3.2 ペイロード設計(外部collector不要)
Burp Collaborator が無いため、cookie窃取+手動再利用ではなく、XSSがadminの認証済みブラウザ内で管理操作を直接実行する方式を採用。
username部に投入したペイロード:
'"><svg/onload=fetch(`/admin/delete?username=carlos`)>
- 先頭
'">で HTML属性/タグを脱出(単一・二重引用符の両コンテキストに対応)。 <svg/onload=...>の onload を空白なしで書く(属性値を引用符で囲まずに済み、レンダリングコンテキスト依存を回避)。文字列はバックティックで表現し引用符衝突を防ぐ。- admin のブラウザは同一オリジン・認証済みのため、
fetchが cookie を自動送出しGET /admin/delete?username=carlosが成立。なお XSS経由の実行はCSRF防御の有無に関わらず成功する(XSSはCSRF対策を迂回する)ため、本実証から「CSRFトークン不要」とは断定できない。確実に言えるのは GET で状態変更(ユーザー削除)ができる設計自体が不備である点。
3.3 実行
クッキー session=<URLエンコードしたペイロード>%3a<token> を付けて /・/my-account?id=wiener・/post?postId=1 にリクエスト(いずれも 500 を返すが username は記録される)。その後ラボ状態をポーリングしたところ、約8〜16秒で admin bot が閲覧 → XSS発火 → carlos削除 → is-solved を確認。
4. 根本原因
| # | 問題 | 説明 |
|---|---|---|
| R1 | 非標準データ構造の未検証 | セッションクッキーの username サブフィールドを、構造化データとして適切にサニタイズ/エスケープせずログに記録・描画している。 |
| R2 | Stored XSS(出力エンコード欠如) | admin閲覧画面で username を HTMLエスケープせず描画している。 |
| R3 | 信頼境界の手前でのログ収集(整合性違反データの保存) | 整合性チェックで拒否(500)するリクエストの改ざん済みフィールドを、検証より前段でログ・分析に保存している(推定)。検証後に保存する設計なら攻撃面が消える。 |
| R4 | 重要操作(ユーザー削除)がGETで実行可能 | /admin/delete が GET で状態変更できる設計。XSS経由ではCSRF防御は迂回されるため、GET/POSTに関わらずXSSがあれば実行されるが、GETで状態変更可能な点自体が不備。 |
5. 影響
- 攻撃者は一般ユーザー権限のみで、admin のブラウザ上で管理操作(ユーザー削除を実証、他の管理GET操作も同様に実行可能と推定)を代行させられる=admin権限での操作代行(confused deputy)。攻撃者がセッション/認証情報を奪取して継続的に admin として振る舞える「乗っ取り」までは本診断では未実証。
- XSSのため管理画面に表示される他ユーザー情報等の機微情報の読み取りも可能。
document.cookieの窃取はHttpOnly属性の有無を未確認のため成否は断定しない。 - 非標準構造ゆえ通常のスキャン/レビューで見逃されやすく、検知が遅れるリスクが高い。
6. CVSS v3.1 評価
公式計算式をPythonで実装して算出(環境値込み)。
| 区分 | スコア |
|---|---|
| Base | 7.4(High) |
| Temporal | 7.1 |
| Environmental | 7.1(High) |
ベクター: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:H/A:N/E:H/RL:O/RC:C/CR:M/IR:M/AR:M
| メトリクス | 値 | 理由 |
|---|---|---|
| AV | N | ネットワーク越しに攻撃可能。 |
| AC | L | 構造解析後は決定論的に発火(ペイロードは安定)。 |
| PR | L | 有効なセッショントークンを得るため一般ユーザーのログインが必要。 |
| UI | R | admin(被害者)がシンク画面を閲覧する必要がある。 |
| S | C | 脆弱コンポーネント(ログ/分析描画)の欠陥が、別権限主体(adminセッション)でのコード実行=管理操作を引き起こす。境界越え。 |
| C | L | XSSにより admin の管理画面に描画される機微情報(他ユーザー情報等)の読み取りが可能なため限定的な機密影響を計上(cookie窃取は HttpOnly 未確認のため根拠にしない)。 |
| I | H | admin権限で carlos を削除=重大なデータ削除(実証)。同シンクから他の管理GET操作も実行可能と推定され、完全性への影響は大。 |
| A | N | 直接の可用性影響なし。 |
| 環境値 | CR:M/IR:M/AR:M | 汎用診断のため資産価値を中位(M)と仮定。 |
7. 対策
優先度順:
- 【根治・R1/R3/R4を同時に無力化】平文フィールドの排除: クッキーに平文 username を持たせず、token のみでサーバ側から username を引く。これで非標準構造のサブフィールド(=挿入点)そのものが消滅する。出力エンコードと並ぶ最上位の根治策。
- 【即効・R2】出力エンコード: admin閲覧画面を含む全描画箇所で username をコンテキストに応じてHTMLエスケープ(フレームワークの自動エスケープを既定有効化)。
- 【多層防御】CSP導入:
Content-Security-Policy(script-src制限・inline/イベントハンドラ禁止)で、エンコード漏れがあってもsvg/onload実行を抑止。 - 【R3】ログ/分析パイプラインの両面安全化: ログ取り込み時にサニタイズし、かつ各コンシューマ(ダッシュボード/SIEM/CSV等)で出力エンコード。描画箇所だけの対策では別コンシューマで再発する。整合性違反データは検証後に(必要なら無害化して)保存する。
- 【R4】重要操作の保護:
/admin/delete等を POST化+CSRFトークン必須にし、GETでの状態変更を不可にする(XSS自体はCSRF防御を迂回するため、本項はR1〜R3と併用して初めて有効)。 - 【運用】非標準構造の継続スキャン: 構造化データ(独自区切りのクッキー等)の各挿入点を定期的にスキャン対象に含める(本ラボの教訓)。
8. 証跡まとめ
| 項目 | 値 |
|---|---|
| クッキー構造 | session=<username>:<token>(平文) |
| 整合性エラー | username改変時 Integrity violation detected(500) |
| XSSシンク | admin閲覧の分析/ログ描画(username未エスケープ) |
| 投入ペイロード | '"><svg/onload=fetch(\/admin/delete?username=carlos`)>` |
| 結果 | admin発火→carlos削除→Solved |
Best regards, (^^ゞ