以下の内容はhttps://cysec148.hatenablog.com/entry/2025/08/15/215316より取得しました。


【有料試作版】PortSwigger LAB解説:DOM XSS in document.write sink using source location.search(select要素内のdocument.writeを悪用)

Hello there, ('ω')ノ

ねらい

このLABは、商品ページの在庫チェッカーで使われているJavaScriptが、URLのlocation.search(クエリ文字列)から値を取り出し、そのままdocument.writeで<select>配下に書き込む、という危険な実装になっている点を突きます。攻撃者はstoreIdというURLパラメータを細工してselect要素から脱出し、任意のHTML要素+イベントハンドラを挿入することでalert(1)を実行します。


全体像(まずはストーリー)

  1. 商品ページのURLにstoreIdを付与すると、その値が<select>の<option>として画面に表示されることを確認。
  2. HTMLをどう書き換えているのか、document.write(シンク)location.search(ソース)を直接使っていることを読み解く。
  3. 文脈は<select>内、かつ<option value="...">の属性値コンテキストなので、まず属性を閉じるためのダブルクォートタグ終端の “>”で脱出。
  4. さらに</select>で親のselectを閉じた後、img onerrorなどの要素を差し込んでalert(1)を起動。
  5. ブラウザでアラートが出ればクリア。

実践:一手ずつ「なぜそうするか」を添えて

1) 「入力が使われている場所」を特定する

  • 操作:任意の商品ページへ移動し、URL末尾に&storeId=abc123のように付加してページを再読込。
  • 観察:ドロップダウン(在庫の店舗選択)にabc123が新しい選択肢として追加されている。
  • なぜ:アプリがlocation.searchからstoreIdを取り出し、document.writeで<option>を生成している証拠です。典型的な脆弱コードは次のイメージです:
  <script>
    var storeId = new URLSearchParams(location.search).get('storeId');
    // 危険:エスケープなしで生挿入
    document.write('<option value="' + storeId + '">' + storeId + '</option>');
  </script>

2) コンテキストを分析する(どこをどう壊す?)

  • 状況:今いるのは<select>の中、かつ<option value="...">の属性値の最中です。
  • 目標:以下の順で脱出します。

    1. " でvalue属性を閉じる
    2. > で<option>開始タグを閉じる
    3. </select> で<select>から脱出
    4. 任意の要素(例:<img src=x onerror=alert(1)>)を挿入して実行

3) ペイロードを設計する(最小ステップで動く形)

  • 人間可読の形(説明用):
  storeId="></select><img src=x onerror=alert(1)>
  • なぜ:

    • 最初の"で value="... をクローズ
    • 続く>で<option>のタグを確定
    • </select>で親コンテナから完全脱出
    • 続けてimg onerrorでスクリプト実行(エラー時にalert)

4) URLエンコードして実際に投下

  • ブラウザに直接貼れるように、スペースなどをエンコードします:
  /product?productId=1&storeId=%22%3E%3C%2Fselect%3E%3Cimg%20src%3Dx%20onerror%3Dalert(1)%3E
  • なぜ:クエリ文字列中の"、<、>、スペース、=などはURLとしてエスケープしておくのが安全確実です。

5) 実行確認

  • 操作:上記URLで商品ページを開く。
  • 観察:ページ読込時にalert(1)が表示される。
  • なぜ:document.writeはHTMLとして解釈・直挿入されるため、selectの外に飛び出した要素のonerrorが即時実行されます。

ペイロード設計の考え方(分解して理解する)

  • ソース: location.search(攻撃者完全制御の入力)
  • シンク: document.write(HTMLとして解釈される最危険シンクの一つ)
  • コンテキスト: select内、optionの属性値 → まず属性を閉じる発想が必要
  • 脱出シーケンス: " → > → </select> → 悪性要素
  • 実行トリガ: onerror(画像読み込み失敗で確実に発火)

つまずきポイント&対処

  • アラートが出ない

    • URLのエンコード漏れがないか確認(特に " < > スペース =)。
    • src=1 でも基本OKだが、確実性のため src=x など実在しない値に。
    • ページがキャッシュしている場合はハードリロード
  • ペイロードがドロップダウンの選択肢としてそのまま見えるだけ

    • 脱出に必要な最初のダブルクォートが欠けている可能性。storeId=" から始めているか見直す。
  • CSPが邪魔している?

    • このLABは通常CSPが緩い想定。もし厳しい環境なら、イベントハンドラ(onerror)やjavascript:URIの可否など、方針を切り替える。

実務目線の防御策

  • document.writeの使用禁止(歴史的負債。動的UIはDOM APIで組み立てる)
  const opt = document.createElement('option');
  opt.value = storeId;          // 値はそのまま代入(HTMLにならない)
  opt.textContent = storeId;    // テキストとして挿入(エスケープ不要)
  select.appendChild(opt);
  • エスケープ/サニタイズの徹底 HTMLとして注入しない。どうしてもinnerHTML等を使うなら、DOMPurifyなどの実績あるライブラリを用いる。
  • 許可リスト(allowlist)による入力検証 storeIdは数値や既知IDのみを受け付ける。
  • CSP(Content Security Policy) 'unsafe-inline'を避け、script-srcでインラインイベントハンドラを無効化。
  • セキュリティレビューで「ソース→シンク」を辿る location、document、storage等の信頼できないソースが、innerHTML、document.writeなどの危険シンクへ渡っていないかを静的・動的に点検。

コピペ用ペイロード(そのまま使える)

  • 説明と同じ内容(スペース等をURLエンコード済み):
/product?productId=1&storeId=%22%3E%3C%2Fselect%3E%3Cimg%20src%3Dx%20onerror%3Dalert(1)%3E
  • ヒューマンリーダブル(ブラウザに貼る前に自分でエンコードしてね):
storeId="></select><img src=x onerror=alert(1)>

まとめ

ポイントは、「どのコンテキストでHTMLが生成されているか」を見極めること。今回は<select>配下の<option value="…">という属性値コンテキストから始まるため、まず属性を閉じてタグを抜ける、次に親要素を閉じる、最後に実行トリガを持つ要素を差し込む、という三段ロケットで成立します。 この「ソース(location.search)→ シンク(document.write)→ コンテキスト(select/optionの属性)→ 脱出シーケンス」という思考パターンは、他のDOM XSSでもそのまま応用できます。

Best regards, (^^ゞ




以上の内容はhttps://cysec148.hatenablog.com/entry/2025/08/15/215316より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14