クライアント側のDOMベースXSS

クライアント側のDOMベースXSS

説明

ブラウザーのJavaScriptがユーザー入力を適切に処理せず、innerHTML、outerHTML、insertAdjacentHTML、document.write などのHTMLを解釈するAPIへ渡すと、DOMベースXSSが発生するおそれがあります。URLパラメーター、フラグメント、要素の値などからマークアップやイベントハンドラーを挿入される可能性があります。例えば、デコードされた ?q=<img src=x onerror=alert(1)> を innerHTML に入れると、JavaScriptが動作するおそれがあります。一度テキストとして扱った値でも、後からHTMLとして再解釈すると危険になります。

想定される影響

  • ユーザーのブラウザーで任意のJavaScriptを実行されるおそれがあります。
  • 読み取り可能なCookie、トークン、localStorage内のデータを盗まれ、アカウントを乗っ取られる可能性があります。
  • DOMの操作によって画面の改ざん、入力の窃取、内部情報の露出が起こるおそれがあります。
  • 偽のフォームやメッセージを使ったフィッシングにつながる可能性があります。

対処方法

  • テキストには textContent や innerText を使い、信頼できない値をHTML解釈用APIへ直接渡さないでください。
  • マークアップが必要な場合は、DOMPurifyやsanitize-htmlなどの信頼できるサニタイザーで制限してください。
  • HTML本文、属性、URL、JavaScriptのそれぞれに合う処理を適用し、異なる文脈へ流用しないでください。
  • URLは URLSearchParams や new URL() で解析し、必要に応じて許可値を検証してください。
  • CSPや、対応する環境ではTrusted Typesを追加の防御として使ってください。

例

変更前

javascript
// 入力を処理せずにDOMへ出力
(function () {
  // フラグメントをDOMへ挿入(実際に解釈される値を確認)
  const raw = location.hash.slice(1); // 例: #<img src=x onerror=alert(1)>
  const box = document.getElementById('msg');
  // BAD: ユーザー入力をHTMLの文脈へ直接挿入
  box.innerHTML = '<p>결과: ' + raw + '</p>';

  // 別の危険: document.writeでユーザー入力を出力
  const q = new URL(location.href).searchParams.get('q') || '';
  document.write('<div>검색어: ' + q + '</div>');
})();

変更後

javascript
(function () {
  const params = new URL(location.href).searchParams;
  const q = params.get('q') || '';
  const box = document.getElementById('msg');

  // 1) HTMLとして解釈せずテキストを表示
  box.textContent = `검색어: ${q}`;

  // 2) HTMLが必要な場合はサニタイズ
  // DOMPurifyをグローバルに読み込んでいる前提
  const userHtml = params.get('html') || '';
  const safeHtml = DOMPurify.sanitize(userHtml, {ALLOWED_TAGS: ['b','i','u']});
  document.getElementById('preview').innerHTML = safeHtml;

  // 3) URLなどの属性に合う形で値を組み立てる
  const link = document.getElementById('link');
  const dest = params.get('dest') || '/';
  // 許可リストによる検証例
  const allowed = ['/', '/help', '/profile'];
  link.setAttribute('href', allowed.includes(dest) ? dest : '/');
})();

解説:

  • 変更前: ユーザーが制御する値をHTML解釈用APIへ渡しています。URLSearchParams はクエリをデコードしますが、location.hash の読み取りはフラグメントをパーセントデコードしません。フラグメント内のエンコードされたタグだけで実行されると判断せず、実際のHTML文字列の生成経路を確認してください。
  • 変更後: textContent でテキストを表示し、許可するマークアップはDOMPurifyで制限します。URL属性もリスト内のパスだけを選びます。DOMPurifyと参照するページ要素は用意されている前提です。

参考資料