DevToolBox

HTMLエスケープとXSS対策の基本

「ユーザー名に <script> と入力されたらアラートが出た」—— XSS(クロスサイトスクリプティング)は、今も OWASP Top 10 に挙がり続ける Web 最頻出の脆弱性です。 対策の土台はシンプルで、ユーザー由来の文字列を HTML に埋め込むときに必ずエスケープすること。 ただし「どの5文字を」「どのコンテキストで」が曖昧なままだと漏れが生まれます。 本記事ではエスケープの基本から、React 時代の注意点、DOMPurify・CSP による多層防御までを整理します。

XSS remains one of the most common web vulnerabilities. The foundation is escaping user-supplied strings before inserting them into HTML — but which characters, and in which context, matters. This guide covers the 5 characters, context-aware escaping, React's auto-escaping and its escape hatches, DOMPurify, and CSP as defense-in-depth.

TL;DR

HTML Entity Encoder

文字列をHTMLエンティティに即座にエンコード/デコード。「この入力がエスケープ後どうなるか」をブラウザ内で確認できます。データ外部送信なし。

今すぐ試す →

1. エスケープすべき5文字 / The 5 characters

文字エンティティ放置した場合のリスク
&&amp;エンティティの誤解釈・二重エスケープの起点
<&lt;タグ開始 = <script><img onerror> の注入
>&gt;タグの終端操作
"&quot;属性値からの脱出(" onmouseover="...)
'&#39;シングルクォート属性からの脱出
// 最小実装(順序が重要: & を最初に)
function escapeHtml(s) {
  return s
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

& の変換を最後にすると、変換済みの &lt;&amp;lt; になる二重エスケープが起きます。順序は & が先です。 実際の攻撃でよく使われるのは <script> よりも<img src=x onerror="fetch(...)"> のようなイベントハンドラ属性で、< のエスケープだけでこの注入は無効化できます。

Escape & first or you will double-escape. Real-world payloads favor event handlers like img onerror over script tags — escaping < neutralizes them.

2. コンテキストが変わればルールも変わる / Context-aware escaping

HTMLエスケープは「HTML本文・引用符付き属性」向けのルールです。 別のコンテキストに同じ文字列を置くなら、別のエンコードが必要になります。

埋め込み先必要な処理典型的な失敗
HTML本文HTMLエスケープ(5文字)innerHTML に未処理文字列
属性値HTMLエスケープ + 必ず引用符で囲む引用符なし属性(value= 区切りで属性追加し放題)
URL(クエリ等)encodeURIComponentjavascript:alert(1) を href にそのまま
JS文字列内JSON.stringify で埋め込み</script> 文字列でスクリプト終端を突破される

特に href / src は要注意です。エスケープしてもjavascript: スキームは文字として正当なので素通りします。 URL を受け取る場所では「http:/https: のみ許可」というスキームの許可リスト検証を入れてください。

Escaping rules depend on context: attribute values need quotes, URLs need encodeURIComponent plus a scheme allowlist (javascript: passes HTML escaping untouched), and strings inside scripts should be embedded via JSON.stringify.

3. React / モダンフレームワークでの注意点 / React and friends

// 安全: JSX の {} は自動エスケープされる
<p>{userInput}</p>

// 危険: HTML として解釈される
<div dangerouslySetInnerHTML={{ __html: userInput }} />

// 危険: スキームは検証されない(React 19 は javascript: を警告/ブロックするが過信しない)
<a href={userInput}>link</a>

// HTML を描画したいなら sanitizer を通す
import DOMPurify from "dompurify";
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userHtml) }} />

JSX interpolation is safe by default. The escape hatches — dangerouslySetInnerHTML, raw hrefs, permissive Markdown renderers — are where XSS re-enters. Sanitize with DOMPurify when you must render HTML.

4. 多層防御: CSP / Defense in depth with CSP

エスケープが第一の防御なら、Content-Security-Policy は「漏れたときに実行させない」第二の防御です。

# インラインスクリプトを禁止し、自ドメイン + nonce のみ許可
Content-Security-Policy: script-src 'self' 'nonce-R4nd0m';
                         object-src 'none';
                         base-uri 'none';

'unsafe-inline' を許可した CSP は XSS 防御としてはほぼ無力です。 nonce(リクエストごとのランダム値)または hash ベースで運用し、 まずは Content-Security-Policy-Report-Only で影響を観測してから強制に切り替えるのが 現実的な導入手順です。

A nonce- or hash-based script-src (never unsafe-inline) limits damage when an escaping bug ships. Roll out with Report-Only first.

まとめ

XSS対策の優先順位は「(1) 出力時に必ずエスケープ(コンテキスト別)→ (2) HTML描画はサニタイザ経由 → (3) CSPで保険」。 フレームワークの自動エスケープに乗りつつ、dangerouslySetInnerHTML と URL だけは 常に疑う——これだけで大半の XSS は防げます。 エスケープ結果の確認には HTML Entity Encoder を、エスケープ済みHTMLの整形確認にはHTML Formatter をどうぞ。

Escape on output per context, sanitize any HTML you must render, and back it all with a strict CSP. Stay suspicious of dangerouslySetInnerHTML and user-supplied URLs.

HTML Entity Encoder

攻撃文字列がエスケープ後どう無害化されるかを実際に確認。エンコード/デコード両対応。

今すぐ試す →

関連ガイド・ツール / Related