説明
textContent、innerText、value、attr などでDOMから取得したテキストや属性値を、後からHTMLとして解釈するとDOM XSSが起こる可能性があります。jQueryの $() は渡した文字列をマークアップとして解析する場合があり、.html() や .append() でもタグやイベントハンドラーを挿入できます。以前エスケープした内容をテキストとして読み出し、再びHTMLとして解釈すると、最初の出力時の保護が失われる場合があります。
想定される影響
- スクリプトから読めるCookie、トークン、ブラウザーストレージの値を盗まれ、ユーザーになりすまされる可能性があります。
- ページ内容、フォーム入力、CSRFトークンなどを読み取られ、外部へ送信されるおそれがあります。
- DOMを改変され、フィッシング用のフォームやポップアップを表示される可能性があります。
- ユーザーの権限でバックエンドAPIやアカウント設定を操作されるおそれがあります。
- 属性、ストレージ、URLに残った値が、再訪問時にも解釈される可能性があります。
対処方法
- DOMのテキストや属性値をHTMLとして解釈せず、
textContent、innerText、jQueryの.text()で表示してください。 - セレクターには
jQuery($str)ではなく$.find(str)を使い、HTMLの解析を避けてください。許可リストで選択範囲も制限してください。 - マークアップが必要なら、DOMPurifyなどの保守されているツールでサニタイズし、必要なタグと属性だけを許可してください。
- 信頼できない文字列を
.html()、.append()、.before()、.after()へ渡さず、用途に応じてテキストノードや安全に作成したDOMノードを使ってください。 - query/hash、
data-*属性、フォーム値は、実際の用途に合わせて検証・サニタイズしてください。 - 追加の防御として、CSPでインラインスクリプトを制限してください。
例
A・B・Cは、テキスト表示、制限したHTML表示、許可した要素の選択という別々の目的を示します。HTMLとして解析しないだけでは選択範囲は制限されないため、許可リストも維持してください。
変更前
javascript
// DOMから読んだ文字列をHTMLとして再解釈して挿入(危険)
$(function () {
// テンプレート・Storage・URLから復元された、ユーザーが制御可能なdata-content
const raw = $("#card").attr("data-content");
// 1) 文字列をそのまま.html()に渡すとイベントハンドラーやスクリプト実行のおそれ
$("#detail").html(raw);
// 2) jQuery($str)がユーザーの文字列をHTMLとして解析する可能性
const $frag = $(raw);
$("#detail").append($frag);
});
変更後
javascript
// 文字列はテキスト、セレクターはセレクターとしてだけ解釈
// 任意のサニタイザー使用例も含む
$(function () {
const raw = $("#card").attr("data-content");
// (A) テキストとして表示
$("#detail").text(raw); // HTMLとして再解釈しない
// (B) HTMLが必要な場合はサニタイザーを使用
// const safe = DOMPurify.sanitize(raw, {ALLOWED_TAGS: ['b','i'], ALLOWED_ATTR: []});
// $('#detail').html(safe);
// (C) ユーザー指定セレクターには$()でなく$.findを使用
const sel = $("#btn").attr("data-target");
// 許可リストやパターン検証の例
const allowed = ["#info", ".section"];
if (allowed.includes(sel)) {
$.find(sel).forEach(function (el) {
el.classList.add("active");
});
}
});
解説:
- 変更前: ユーザーが制御できる属性値を
.html()や$()に渡すと、マークアップとして解釈されます。イベントハンドラーを含む値でスクリプトを実行されるおそれがあります。 - 変更後:
.text()はHTMLとして解釈せずに表示します。必要なマークアップはDOMPurifyでサニタイズし、セレクターは$.findと許可リストで扱います。