HTMLエスケープは、& < > " ' といった「HTMLとして特別な意味を持つ文字」を、表示上は同じに見えるエンティティ(文字参照)に置き換える処理です。これを怠ると、ユーザーが入力した文字列がそのままタグやスクリプトとして解釈され、XSS(クロスサイトスクリプティング)という深刻な脆弱性につながります。本記事では、何を・なぜ変換するのか、XSS の仕組みと種類、そして出力コンテキストに応じた正しい防ぎ方までを正確に整理します。
1. HTMLエスケープとは — 5つの特殊文字をエンティティに
ブラウザは HTML を読むとき、特定の文字を「構造を表す記号」として扱います。たとえば < は「ここからタグが始まる」という合図で、& は「ここから文字参照が始まる」という合図です。これらの文字を文字そのものとして表示したいときは、エンティティに置き換える必要があります。これが HTMLエスケープです。
HTMLエスケープで対象になる代表的な文字は次の5つです。コードや記号はすべてエンティティ表記で示します。
| 文字 | 名前 | エンティティ(名前付き) | 数値文字参照 |
|---|---|---|---|
& | アンパサンド | & | & |
< | 小なり | < | < |
> | 大なり | > | > |
" | ダブルクォート | " | " |
' | シングルクォート | '(または ') | ' |
& を & に変換します。先に < を < に変えてから & を変換すると、いま生成した & まで二重に変換され(&lt; のように)壊れてしまうためです。
たとえば、ユーザーが <b>太字</b> と入力したとします。これを エスケープせずに出力すると、ブラウザは <b> をタグとみなして「太字」を実際に太字で表示します。エスケープして <b>太字</b> として出力すれば、画面には文字どおり <b>太字</b> という文字列が表示されます。
2. なぜ必要か — ユーザー入力がHTMLとして解釈される危険
Webアプリの多くは、ユーザーが入力した値(名前・コメント・検索キーワードなど)を画面に表示します。問題は、その値がそのまま HTML に埋め込まれる場合です。ブラウザは「これはデータ」「これは命令(タグ)」を区別できず、埋め込まれた文字列をHTMLの一部として解釈してしまいます。
仮にコメント欄へ次のような文字列が投稿されたとします(エンティティ表記で示します)。
<script>alert(document.cookie)</script>
これをエスケープせずページに出力すると、ブラウザは <script> を本物のスクリプトタグとして実行します。結果として、そのページを開いた他のユーザーのブラウザ上で、攻撃者の用意したコードが動いてしまうのです。これがXSSの基本構図です。
3. XSS(クロスサイトスクリプティング)の仕組みと種類
XSS は、攻撃者の文字列がページ内でスクリプトやHTMLとして実行される脆弱性です。実行されると、Cookieやアクセストークンの窃取、画面の改ざん、フォームの偽装、利用者になりすました操作などが可能になります。XSSは入り込み方によって大きく3種類に分けられます。
| 種類 | 攻撃文字列の経路 | 特徴 |
|---|---|---|
| 反射型(Reflected) | URLパラメータ等で送られ、応答にそのまま反射される | 罠リンクを踏ませる必要がある。検索結果やエラー表示で起きやすい |
| 格納型(Stored) | DB等に保存され、後で他ユーザーに配信される | 掲示板やコメント欄で発生。閲覧者全員に影響が及び被害が大きい |
| DOM型(DOM-based) | サーバを介さず、ブラウザ内のJavaScriptが危険な処理に渡す | innerHTML や location 等の扱いが原因。サーバ側のエスケープだけでは防げない |
反射型と格納型はサーバが文字列を出力する瞬間のエスケープ漏れが原因です。DOM型は、サーバから来たHTMLは正しくても、ブラウザ内のJavaScriptがユーザー由来の値を innerHTML などへ無加工で渡すことで発生します。つまり「サーバ側だけ直せば安全」ではない点に注意が必要です。
4. エスケープで防ぐ — 出力コンテキストに応じた対策
XSS対策の本質は「値を出力する場所に合わせてエスケープする」ことです。同じユーザー入力でも、HTML本文に置くのか、属性値に置くのか、JavaScriptの中に置くのかで、危険な文字も必要なエスケープも変わります。
HTML本文(要素の中身)
最低限 & < > を変換します。これでタグの開始を封じられます。
属性値(attribute)
属性は必ずクォートで囲み、本文の3文字に加えて、囲んでいるクォート(" または ')を変換します。クォートをエスケープしないと、"><script>... のように属性を抜け出して新しいタグを差し込まれます。たとえば値が x" onmouseover="alert(1) のような場合、属性を脱出してイベントハンドラを注入されてしまいます。
JavaScript文脈
ユーザー入力を <script> 内に直接埋め込むのは避けるのが原則です。どうしても渡す場合は、HTMLエスケープではなくJavaScriptとして安全な形(たとえばサーバ側で JSON.stringify 相当のエンコードを行い、加えて </script> 等を無害化)にします。HTML用のエスケープをそのまま流用しても安全にはなりません。
URL文脈
href や src にユーザー値を入れる場合は、URLエンコードに加えてスキームの検証が必要です。javascript: から始まるURLはクリックでスクリプトを実行するため、http / https など許可したスキームだけを通します。
5. エンティティの種類 — 名前付きと数値文字参照
エスケープ後の表現である「文字参照」には2系統あります。どちらも表示結果は同じ文字になります。
- 名前付き文字参照:
<>&"のように、覚えやすい名前を使う方式。読みやすい反面、定義された名前しか使えません。 - 数値文字参照:文字のコードポイントを使う方式で、10進数(
<)と16進数(<)があります。任意の文字を表現でき、名前付きが存在しない文字にも使えます。
いずれの形式も末尾のセミコロン ; を付けるのが正しい書き方です。なお '(シングルクォートの名前付き)は HTML では広く使えますが、互換性の都合から実務では数値の ' が好まれることがあります。エスケープを自分で実装するより、言語やフレームワークの標準関数を使うのが安全かつ確実です。
<script> のような表現で検出をすり抜けようとすることがあります。独自のブラックリスト(特定の文字列を弾く方式)でXSSを防ごうとしないでください。正攻法は、出力時に必ずエスケープ(またはサニタイズライブラリ)を通すことです。
6. 実務 — フレームワークの自動エスケープとCSP
現代のテンプレートエンジンやUIフレームワーク(React、Vue、各種サーバサイドテンプレート等)は、変数を埋め込むと既定でHTMLエスケープします。多くのXSSは、この自動エスケープを「生のHTMLを挿入する機能」で明示的に無効化したときに発生します。生HTML挿入の機能(innerHTML 相当)は、信頼できない値には使わないことが大原則です。
- 自動エスケープを信頼する:テンプレートの標準の埋め込み構文を使い、生HTML挿入は避ける。どうしても必要なら専用のサニタイズライブラリ(許可したタグ・属性だけを残す)を通す。
- コンテキストに合った出力を選ぶ:属性・URL・JavaScript には、それぞれ専用のエスケープ/エンコードを使う。
- CSP(Content-Security-Policy)を併用する:HTTPヘッダで実行を許可するスクリプトの出所を制限する多層防御。インラインスクリプトを禁止すれば、万一の注入があっても実行を抑えられます。あくまでエスケープを補完する「保険」であり、単独の対策ではありません。
よくある質問(FAQ)
HTMLエスケープとは何ですか?
HTMLとして特別な意味を持つ文字を、表示上は同じに見えるエンティティ(文字参照)に置き換える処理です。具体的にはアンパサンド、小なり、大なり、ダブルクォート、シングルクォートの5文字を、& < > " ' のような形に変換します。これにより、文字列がタグや属性として解釈されず、あくまで「文字」として表示されるようになります。
XSS(クロスサイトスクリプティング)とは何ですか?
攻撃者が用意した文字列が、Webページ内でスクリプトやHTMLとして解釈・実行されてしまう脆弱性です。エスケープされないユーザー入力がそのまま画面に出力されると、攻撃者のscriptタグが動作し、Cookieやトークンの窃取、画面の改ざん、なりすまし操作などが可能になります。反射型・格納型・DOM型の3種類に大別されます。
どの文字を変換すればよいですか?
HTML本文では最低限 &(アンパサンド)、<(小なり)、>(大なり)の3つを、属性値の中ではさらに "(ダブルクォート)と '(シングルクォート)を変換します。& を最初に変換するのが定石です。ただし安全に必要なエスケープは出力コンテキスト(HTML本文か、属性か、JavaScript内か、URL内か)によって変わるため、文脈に応じたエスケープを行うことが重要です。