セキュリティ · 2026年9月7日

HTMLエンティティ — いつエスケープし、いつアンエスケープし、なぜXSSはエスケープされた文字1つ分だけ離れているか

HTMLエンティティは特殊文字を安全なシーケンスに変換します。XSSを防止し、コンテンツの整合性を保持し、すべての開発者を少なくとも1回惑わせます。完全な全体像を解説します。

すべてのWebセキュリティの議論は最終的に同じスライドにたどり着きます:ユーザー入力をHTMLに挿入する前に必ずエスケープしてください。 その理由はHTMLエンティティです — <script>を&lt;script&gt;に変換し、ユーザーのコメントがXSS攻撃になることを防ぐメカニズムです。

しかしエスケープは半分だけです。アンエスケープ — &amp;を&に戻す — も同样に重要で、間違えると同样に危険です。この記事では両方向、重要な文字、および実際の脆弱性につながるエラーを扱います。

重要な5つの文字

HTMLには構造的に重要な5つの文字があります — タグ、属性、エンティティを区切ります。これらがユーザーコンテンツに現れ、エスケープされない場合、ブラウザは它们をHTMLマークアップとして解釈します:

文字 エンティティ 重要な理由
< &lt; タグを開始します。<script>はタグです。
> &gt; タグを終了します。
& &amp; エンティティを開始します。&lt;はエンティティです。
" &quot; 属性値を区切ります。
' &#39; 単一引用符で囲まれた属性を区切ります。

HTML要素のコンテンツにテキストを挿入する場合、<、>、&をエスケープする必要があります。属性値にテキストを挿入する場合、"もエスケープする必要があります(属性が単一引用符を使用する場合は'も)。

エンティティの仕組み

HTMLエンティティはブラウザが単一文字として解釈する特別なシーケンスです:

  • 名前付きエンティティ: &amp;、&lt;、&gt;、&quot;、&nbsp; — 読みやすい、限定されたセット。
  • 数値エンティティ: &#60;(10進)、&#x3C;(16進) — コードポイントによる任意のUnicode文字。
  • 記号用の名前付きエンティティ: &copy;(©)、&euro;(€)、&mdash;(—) — 便利な名前。

&amp;エンティティは特殊で、&自体のエスケープです。最初に&をエスケープし、次に<と>をエスケープすると、二重エスケープしません。順序が重要です:

正しい:  &amp; → &amp;amp;  →  &amp;amp;lt;   (正しい)
間違った: &lt; → &amp;lt;   →  &amp;amp;lt;   (二重エスケープ)

常に最初に&をエスケープしてください。次に他の文字を。そうすれば二重エスケープしません。

エスケープが必要な場所

ユーザー生成コンテンツ。 コメント、フォーラム投稿、レビュー、メッセージ — ユーザーから来てページに表示されるすべてのテキストはエスケープする必要があります。<img src=x onerror=alert(1)>を含むコメントは、<と>をエスケープしないとXSS攻撃です。

属性内のデータ。 ユーザーデータをhref、src、alt、title属性に配置する場合、エンティティ文字をエスケープしてください。foo" onclick="alert(1)のような値は属性から脱出し、イベントハンドラを注入します。

JavaScript文字列補間。 JavaScript内でHTMLを構築する場合:

// 危険:生の補間
element.innerHTML = `<p>${userComment}</p>`;

// 安全:エスケープされた補間
element.innerHTML = `<p>${escapeHtml(userComment)}</p>`;

innerHTMLは文字列をHTMLとして解釈します。userCommentにマークアップが含まれている場合、実行されます。HTMLに補間する前に必ずエスケープしてください。

サーバーサイドレンダリング。 テンプレートエンジン(Handlebars、Jinja、EJS、Razor)は通常デフォルトで自動エスケープします。しかし「safe」または「raw」フィルター(Handlebarsの三重波括弧{{{やDjangoの|safeなど)を使用する場合、エスケープをオプトアウトしています。コンテンツを制御する場合のみ行ってください。

いつアンエスケープするか

アンエスケープはプロセスを逆にします:&lt;→<、&amp;→&。必要な場合:

  • エンコードされたデータの表示。 APIがエスケープされたHTMLを返す場合(レンダリングを防ぐために这样做するものがあります)、表示する前にアンエスケープする必要があります。
  • フォームデータの処理。 フォーム送信にはURLエンコードまたはエンティティエンコードされた値が含まれる場合があります。
  • ユーザー入力の解析。 ユーザーがHTMLエンコードされたテキストを貼り付け、そのまま表示したい場合、最初にアンエスケープし — HTMLに再挿入する場合は再エスケープしてください。

危険:表示する直前にのみアンエスケープし、保存する際には绝対にアンエスケープしないでください。 元のエスケープされた形式を保存してください。レンダリング時にアンエスケープしてください。

XSS:エスケープされた文字1つ分だけ離れている

クロスサイトスクリプティング(XSS)は、エスケープされていないユーザー入力がブラウザのHTMLパーサーに到達した場合に発生します。最も一般的なバリアント:

保存型XSS。 ユーザーが<script>steal(document.cookie)</script>をコメントとして送信します。サーバーはそれをエスケープせずに保存します。コメントを表示するすべての訪問者がスクリプトを実行します。

反射型XSS。 URLが?q=<script>alert(1)</script>を含んでいます。サーバーはqパラメータをエスケープせずにページに反映します。リンクをクリックする誰もがスクリプトを実行します。

DOMベースXSS。 JavaScriptがlocation.hashを読み取り、innerHTMLを介してDOMに挿入します。ハッシュにマークアップが含まれています。ブラウザがそれをレンダリングします。

3つすべてが同じことで防止されます:HTMLにコンテンツを挿入する前に<、>、&、"、'をエスケープしてください。

エスケープ関数

すべての言語にあります。パターンは同じです:

function escapeHtml(str) {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

順序が重要です:最初に&、次に他の文字。&の前に<をエスケープすると、&lt;ではなく&amp;lt;になります。

ハイスループットシステムにはライブラリを使用してください。DOMPurifyはHTMLをサニタイズします(安全なタグを許可し、危険なものを削除)。Webプラットフォームの組み込みDOMParserとtextContentも暗黙的にエスケープを処理します。

よくあるエラー

  • <の後に&をエスケープ — 二重エスケープを引き起こします:&lt;ではなく&amp;lt;になります。
  • 属性コンテキストを忘れる — <と>をエスケープするが"をエスケープしないと、属性インジェクションが可能になります。
  • エスケープなしでinnerHTMLを使用 — 最も一般的なXSSベクトル。
  • サーバー応答を信頼 — 「サニタイズされた」HTMLを返すAPIはすべてをサニタイズしない場合があります。クライアント側でもエスケープしてください。
  • 早すぎるアンエスケープ — 保存時にアンエスケープすると、生のペイロードがデータベースに残り、レンダリングが再エスケープを忘れた場合に実行准备好了します。

試してみる

デコードする必要があるHTMLエンコードされたテキストや、HTMLに安全に挿入する必要がある生のテキストがある場合は、ブラウザベースのツールを使用して変換をローカルで維持してください。