説明
ユーザーが制御する文字列をjQueryの html()、append()、prepend()、wrap()、wrapInner()、wrapAll()、before()、after() にHTMLとして渡すと、悪意のあるマークアップが実行されるおそれがあります。globalEval() は文字列をコードとして実行し、getScript() はURLからスクリプトを取得して実行するため、コードとURLの信頼性をそれぞれ確認する必要があります。
想定される影響
- 悪意のあるスクリプトにより、読み取り可能なCookieや認証情報が漏えいするおそれがあります。
- セッションを乗っ取られ、ユーザーの権限を悪用される可能性があります。
- 挿入されたHTMLやJavaScriptでフィッシング画面を表示されるおそれがあります。
対処方法
- テキストには
text()、フォームの値にはval()など、用途に合うAPIを使ってください。 - HTML本文に入れるテキストはエンコードし、実際のHTMLを許可する場合は信頼できるサニタイザーで制限してください。
globalEval()に外部入力を渡さず、getScript()には固定された信頼できるスクリプトURLだけを使ってください。HTMLエスケープは、コード実行の保護やスクリプトURLの検証を代替しません。
例
変更前
javascript
// 入力をDOM操作メソッドに直接渡す
function vulnerable(userInput) {
$("#container").html(userInput); // XSSのリスク
}
// 別の例
const hash = window.location.hash;
$("div").append(hash); // HTMLとして解釈される実際の値によってリスクが異なる
変更後
javascript
// テキストの表示にはtext()を使用
function safe(userInput) {
$("#container").text(userInput); // テキストとして表示
}
// またはHTMLへ挿入する前にエスケープ
function escapeHTML(str) {
return String(str).replace(/[&<>'"]/g, function (c) {
return {
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'",
}[c];
});
}
function safeWithEscape(userInput) {
$("#container").html(escapeHTML(userInput)); // エスケープ後に挿入
}
解説:
- 変更前: 信頼できない値をHTMLとして解釈するAPIへ渡しています。現在のブラウザーはURLフラグメントを読み取るときにパーセントデコードしないため、
window.location.hashにエンコードされたタグがあるだけで実行されるわけではありません。別途行うデコードも含め、実際のHTML値がどう作られるかを確認してください。 - 変更後:
text()またはHTML本文用のエスケープによって、この出力位置では値をテキストとして表示します。