HTMLエンティティとは?
HTMLエンティティとは、HTMLドキュメントの中で特殊な意味を持つ文字や、通常のキーボードで入力しにくい文字を表現するための特別なテキストシーケンスです。エンティティは &(アンパサンド)で始まり、;(セミコロン)で終わります。その間には、説明的な名前(名前付きエンティティ — & など)または文字のコードポイント(数値エンティティ — & や &)が入ります。
HTMLエンティティは単なる文字表現のための仕組みではありません。Webセキュリティの根幹を成す重要な概念です。ユーザー入力、CMSのデータ、メールテンプレート、テンプレートエンジンなど、動的なコンテンツを扱うすべてのWeb開発者にとって、HTMLエンティティの仕組みを深く理解することは不可欠です。
文字エンコーディングの歴史
ASCII時代(1960年代〜1980年代)
ASCII(American Standard Code for Information Interchange)は128文字を定義しました。英文字(大文字・小文字)、数字、句読点、制御文字です。アメリカ英語には十分でしたが、世界の他の言語には全く不十分でした。
Latin-1 / ISO-8859-1(1980年代〜1990年代)
ISO-8859-1(Latin-1とも呼ばれます)は、8ビット目を使ってASCIIを256文字に拡張し、西ヨーロッパ言語で使われるアクセント付き文字(é、ü、ñなど)を追加しました。HTML 2.0とHTML 3.2はLatin-1を参照文字セットとして正式に採用し、é(é)、ü(ü)などの名前付きエンティティが定義されました。
しかし256文字では日本語、アラビア語、中国語、韓国語などの文字体系を到底カバーできません。各地域が互換性のない独自エンコーディング(Shift-JIS、Big5、EUC-JPなど)を開発し、エンコーディングが混在した際に文字化けが起きるいわゆる「文字化け問題」が生まれました。
UnicodeとUTF-8(1991年〜現在)
Unicode Consortiumは1991年に最初のUnunicode標準を発表しました。目的はすべての文字体系のすべての文字に固有のコードポイントを割り当てることです。現在Unicodeは150以上の文字体系にわたる14万字以上をカバーしています。
UTF-8は1992年にKen ThompsonとRob Pikeによって考案され、Unicodeのコードポイントを1〜4バイトでエンコードし、ASCIIと後方互換性を持ちます。2000年代にWebの主要エンコーディングとなり、2024年時点で98%超のウェブページがUTF-8を使用しています。
UTF-8の時代でもエンティティが必要な理由
UTF-8でどんな文字でもエンコードできるなら、なぜエンティティが必要なのでしょうか。3つの理由があります:
- 予約済み文字:
<、>、&はHTMLマークアップで特別な意味を持ちます。UTF-8ドキュメントでも、これらをリテラル表示するにはエスケープが必要です。 - 属性区切り文字:
"と'は属性値を囲むために使われ、属性値内に含める場合はエスケープが必要です。 - 空白制御:
(ノーブレークスペース)は通常のスペースでは実現できないレイアウト制御を提供します。
基本概念:HTMLエンティティの仕組み
名前付きエンティティ
名前付きエンティティは最も可読性の高い形式で、文字の説明に由来するニーモニック名を使用します。HTML5では2,000以上の名前付きエンティティが定義されています。
<!-- 名前付きエンティティの使用例 -->
<p>パンとバター:Bread & Butter</p> <!-- 表示:Bread & Butter -->
<p>3 < 5 かつ 10 > 7</p> <!-- 表示:3 < 5 かつ 10 > 7 -->
<p>Copyright © 2026</p> <!-- 表示:Copyright © 2026 -->
<p>価格:49€</p> <!-- 表示:価格:49€ -->
数値エンティティ:十進数と十六進数
あらゆるUnicode文字を十進数または十六進数のコードポイントで参照できます:
- 十進数:
&#の後に十進数のコードポイント — 例:<は<(U+003C) - 十六進数:
&#xの後に十六進数のコードポイント — 例:<は<
どちらも同等です。Unicodeコードポイントは通常十六進数で表されるため(U+003C)、技術文書では十六進数形式がよく使われます。
<!-- 以下の3つはすべて等価で、< を表示します -->
<
<
<
セキュリティ上の重要な5つのエンティティ
この5文字がHTMLインジェクション防御の基盤となります:
| 文字 | 名前付きエンティティ | 十進数 | 十六進数 | 意味 |
|---|---|---|---|---|
< |
< |
< |
< |
HTMLタグの開始 |
> |
> |
> |
> |
HTMLタグの終了 |
& |
& |
& |
& |
エンティティの開始 |
" |
" |
" |
" |
ダブルクォート属性 |
' |
' |
' |
' |
シングルクォート属性 |
HTMLエンティティ参照テーブル
| 文字 | 名前付きエンティティ | 十進数 | 十六進数 | 用途 |
|---|---|---|---|---|
< |
< |
< |
< |
タグ区切り文字 |
> |
> |
> |
> |
タグ区切り文字 |
& |
& |
& |
& |
エンティティ前置詞 |
" |
" |
" |
" |
属性値 |
' |
' |
' |
' |
属性値 |
|
|
  |
  |
ノーブレークスペース |
© |
© |
© |
© |
著作権 |
® |
® |
® |
® |
登録商標 |
™ |
™ |
™ |
™ |
商標 |
€ |
€ |
€ |
€ |
ユーロ記号 |
— |
— |
— |
— |
エムダッシュ |
– |
– |
– |
– |
エヌダッシュ |
XSS対策:エンティティエンコーディングがサイトを守る理由
クロスサイトスクリプティング(XSS)は最も一般的なWebセキュリティ脆弱性の一つです。攻撃者が悪意のあるスクリプトを他のユーザーが閲覧するコンテンツに注入することで発生します。HTMLエンティティエンコーディングはXSSに対する主要な防御手段です。
典型的なXSS攻撃の例
ユーザーの検索クエリをそのまま表示する機能を考えてみましょう:
<!-- 脆弱:ユーザー入力を直接HTMLに挿入している -->
<p>検索キーワード:<?php echo $_GET['q']; ?></p>
攻撃者が次のようなURLを作成します:
https://example.com/search?q=<script>document.cookie</script>
ブラウザは <script> タグをレンダリングして攻撃者のコードを実行します。document.cookie でセッショントークンを盗んだり、fetch() でデータを攻撃者のサーバーに送信したりできます。
対策:出力時にエンコード
<!-- 安全:すべての出力をエンコードする -->
<p>検索キーワード:<?php echo htmlspecialchars($_GET['q'], ENT_QUOTES, 'UTF-8'); ?></p>
ブラウザには次のように見えます:
<p>検索キーワード:<script>document.cookie</script></p>
スクリプトはテキストとして表示されるだけで、実行されません。
実践的なコード例
JavaScript:安全なDOM操作
JavaScriptでユーザー生成コンテンツを挿入する最も安全な方法は textContent を使うことです。textContent はHTMLを一切解釈しません:
// 安全:textContent はHTMLを解釈しない
const el = document.getElementById('output');
el.textContent = userInput; // すべて自動的にエスケープされる
// 危険:innerHTML はHTMLを解析・実行する
el.innerHTML = userInput; // 信頼できない入力には絶対使わない
JavaScriptでHTML文字列を組み立てる必要がある場合は、必ず先にエスケープします:
function escapeHtml(str) {
return str
.replace(/&/g, '&') // & を最初に処理すること
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
const safe = `<p>検索結果:${escapeHtml(userInput)}</p>`;
注意:& を必ず最初に置換してください。< を先に置換すると、< の中の & が再度エスケープされ &lt; になる二重エンコードが発生します。
PHP:htmlspecialchars() と htmlentities()
PHPにはHTMLエンコードのための主要な関数が2つあります:
// htmlspecialchars:5つの重要な文字のみエンコード
$safe = htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
// htmlentities:名前付きエンティティに対応するすべての文字をエンコード
$safe = htmlentities($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
重要な違い:htmlspecialchars() は5つの危険な文字のみエンコードします。htmlentities() はアクセント付き文字や記号(例:é → é)もエンコードします。UTF-8ドキュメントでは通常 htmlspecialchars() が推奨されます。UTF-8はすべての文字を直接表現できるため、危険な文字だけをエスケープすれば十分です。
ENT_QUOTES を渡してシングルクォートとダブルクォートの両方をエンコードし、文字セットとして必ず 'UTF-8' を指定してください。
Python:html.escape()
import html
# 基本的なエスケープ
safe = html.escape(user_input)
# シングルクォートもエスケープ(Python 3.2以降はデフォルトでTrue)
safe = html.escape(user_input, quote=True)
# 使用例
user_input = '<script>alert("XSS")</script>'
print(html.escape(user_input))
# 出力:<script>alert("XSS")</script>
テンプレートエンジン(Jinja2、Django、Handlebars)
ほとんどのモダンなテンプレートエンジンはデフォルトで自動エスケープを行います:
<!-- Jinja2 / Django:デフォルトで自動エスケープ -->
<p>{{ user_comment }}</p>
<!-- 生のHTMLをレンダリング(ユーザー入力には危険!)-->
<p>{{ user_comment | safe }}</p>
<!-- Handlebars:二重波括弧はエスケープ、三重はしない -->
<p>{{userComment}}</p> <!-- エスケープ済み — 安全 -->
<p>{{{userComment}}}</p> <!-- 生のHTML — 危険! -->
実際のユースケース
1. 技術ドキュメントとコードブログ
HTMLについて書く際、<、>、& を含むコード例を表示する必要がよくあります。エンティティを使えばページ構造を壊すことなくリテラル文字として表示できます:
<pre><code>
<div> と </div> でセクションを囲みます。
& 文字はHTMLエンティティの開始を表します。
</code></pre>
2. CMSとユーザー生成コンテンツ
ユーザー生成テキストを保存・表示するCMSは、ページに出力する前に必ずHTMLエンティティのエンコードを行わなければなりません。ブログのコメント、フォーラム投稿、商品レビュー、SNSの投稿などが対象です。この処理を怠ることが、実際のXSS攻撃の大部分の原因となっています。
3. HTMLメールテンプレート
HTMLメールのレンダリングはクライアントによって大きく異なります。タイポグラフィ用の文字に名前付きエンティティ(—、‘、’、…)を使用することで、Gmail、Outlook、Apple Mailなどのクライアントで一貫したレンダリングが期待できます。
4. タイポグラフィと特殊記号
エンティティはキーボードで入力しにくいタイポグラフィ文字に確実にアクセスする手段を提供します:
<p>エムダッシュ—補足説明に使われます—はハイフンより表現力があります。</p>
<p>彼女は“こんにちは”と言って微笑んだ。</p>
<p>価格:29 €</p>
<!-- で数字と通貨記号が別の行に分かれるのを防ぐ -->
5. レガシーシステムの国際化対応
UTF-8を確実にサポートできないレガシーシステムでは、数値エンティティを使ってあらゆるUnicode文字を表現できます:
<!-- 漢字「龍」(U+9F99)の十進数エンティティ -->
龙
<!-- ひらがな「あ」(U+3042) -->
あ
名前付きエンティティ vs 数値エンティティ
| 観点 | 名前付き(<) |
十進数(<) |
十六進数(<) |
|---|---|---|---|
| 可読性 | 高い | 中程度 | 低い |
| カバー範囲 | 約2,000文字 | 全Unicode | 全Unicode |
| HTML5サポート | 完全 | 完全 | 完全 |
| XMLサポート | 5つのみ定義済み | 完全 | 完全 |
| 最適用途 | 一般的な文字 | 任意のUnicode | 技術文書・Unicode参照 |
HTMLとXMLのエンティティの重要な違い
XMLは5つのエンティティのみ事前定義しています(<、>、&、"、')。© や などの名前付きエンティティは、DTDで宣言されない限りXMLでは未定義です。
<!-- XML では無効(未定義のエンティティ):-->
<p>Copyright © 2026</p>
<!-- XML でも有効(数値エンティティ):-->
<p>Copyright © 2026</p>
<!-- HTML5 ではどちらも有効 -->
XHTMLやSVGを書く場合、基本の5つ以外の文字には数値エンティティを使うか、UTF-8の文字をそのまま使用してください。
ベストプラクティス
1. スタック全体でUTF-8を使用する
データベースの照合順序、HTTPの Content-Type ヘッダー、HTMLの <meta charset> タグのすべてでUTF-8を宣言することで、非ASCII文字のエンティティエンコードの必要性をなくせます:
<meta charset="UTF-8">
header('Content-Type: text/html; charset=UTF-8');
2. コンテキストに応じた適切なエンコードを行う
インジェクションのコンテキストが異なれば、必要なエスケープも異なります:
- HTMLボディ:
<、>、&をエスケープ - HTML属性:
<、>、&、"、'をエスケープ - JavaScript文字列:
\uXXXXエスケープまたはJSONエンコードを使用 - CSSの値:異なるエスケープルールが適用される
- URL:パーセントエンコーディングを使用(
%3C、<ではない)
あるコンテキスト用のエンコードが他のコンテキストで安全とは限りません。
3. 入力時ではなく出力時にエンコードする
データベースには生のデータを保存し、HTMLへの出力時にエンコードします。入力時にエンコードすると、出力時に二重エンコードが発生するリスクがあり、JSON API、プレーンテキストのメールなど非HTMLのコンテキストでデータが正しく使えなくなります。
4. 信頼できない入力を処理前にデコードしない
セキュリティフィルターを適用する前にユーザー提供のエンティティをデコードすると防御が無効化されます。<script> をデコードすると <script> になり、単純な「山括弧を禁止する」フィルターを簡単に回避できます。
5. 二重エンコードを避ける
二重エンコード(&lt; は < ではなく < として表示される)は、アプリケーションの複数のレイヤーがそれぞれ独立してエンコードしてしまう際によく起きるミスです。エンコードはプレゼンテーション層の一箇所に集約してください。
6. HTML4における ' の問題
'(アポストロフィ)はXMLとXHTMLでは定義されていますが、HTML4では定義されていません。HTML4の環境では代わりに ' を使ってください。HTML5では ' が正式に名前付きエンティティリストに追加されました。
よくある質問
Q:すべての特殊文字をエンコードする必要がありますか?
セキュリティの観点からは、少なくとも5つの重要な文字(< > & " ')をエンコードする必要があります。著作権マーク、ダッシュ、通貨記号などのタイポグラフィ文字については、UTF-8ドキュメントではUTF-8文字をそのまま使用しても全く問題ありません。エンティティはレガシーシステムや文字エンコーディングを保証できない環境でより重要になります。
Q:& と & の違いは何ですか?
& はリテラルのアンパサンド文字で、& はそのHTMLエンティティ表現です。HTMLソースコードでリテラルの & を表示したい場合は必ず & と書く必要があります。単語の前にそのまま & を書くと、ブラウザがエンティティの開始として解釈しようとして、レンダリングが正しく行われない可能性があります。
Q: は通常のスペースとなぜ動作が異なるのですか?
通常のスペース(U+0020)は「改行可能スペース」です。ブラウザはここで改行でき、連続する複数のスペースは1つに折りたたまれます。 (ノーブレークスペース、U+00A0)は前後の文字間の改行を防ぎ、折りたたまれません。「100 km」や「田中 太郎」などを同じ行に保つのに役立ちます。
Q:絵文字に数値エンティティを使えますか?
はい。絵文字にはUnicodeコードポイントがあり、数値エンティティで表現できます。例えば😀(U+1F600)は十六進数で 😀、十進数で 😀 です。UTF-8ドキュメントでは絵文字を直接貼り付けることができますが、数値エンティティはフォールバックとして機能します。
Q:href 属性に特有のXSSリスクはありますか?
はい。href 属性には特別な危険があります。URLは javascript: プロトコルを使用できるため、HTMLエンコードだけでは不十分です:
<!-- < と > がエンコードされていても危険: -->
<a href="javascript:alert(1)">クリック</a>
<!-- 安全なアプローチ:プロトコルを検証する -->
<?php
$url = $_GET['url'];
if (!preg_match('/^https?:\/\//i', $url)) { $url = '#'; }
echo '<a href="' . htmlspecialchars($url) . '">リンク</a>';
?>
Q:モダンなJavaScriptフレームワークはHTMLエンコードを自動的に処理しますか?
はい — React、Vue、Angular、Svelteなどのモダンフレームワークはデフォルトで出力をエスケープします。ReactのJSXは {} で補間した値を自動的にエスケープします。ただし、各フレームワークにはエスケープを明示的に無効化する手段(Reactの dangerouslySetInnerHTML、Vueの v-html)があり、信頼できるコンテンツに対してのみ極めて慎重に使用する必要があります。
Q:HTMLエンティティとURLエンコーディングの違いは何ですか?
HTMLエンティティはHTMLドキュメント内でのテキスト表現のためのものです(< は < を表示)。URLエンコーディング(パーセントエンコーディング)はURL内の特殊文字を安全に送信するためのものです(%3C は < を表す)。両者を混同して間違ったコンテキストで使用すると、セキュリティホールや表示の問題が生じます。
まとめ
HTMLエンティティはWeb開発に不可欠なメカニズムです:
- セキュリティ —
<、>、&、"、'をエスケープしてXSSインジェクションを防ぐ - 正確性 — HTMLで予約された意味を持つ文字をリテラルとして表示する
- 互換性 — レガシーまたは制約のある環境で任意のUnicode文字を表現する
- タイポグラフィ — エムダッシュ、ノーブレークスペース、通貨記号などの特殊文字を確実に挿入する
モダンなUTF-8スタックでは、動的なコンテンツをHTMLに出力する際に主に5つのセキュリティ上重要な文字をエンコードする必要があります。 や — などの名前付きエンティティはタイポグラフィ上も依然として有用です。名前付きエンティティと数値エンティティの違い、そしてHTMLとXMLのルールの相違点を理解することで、より効率的でセキュリティ意識の高いWeb開発者になれるでしょう。