HTMLエンティティエンコードとURLエンコードの違い
HTMLエンティティエンコードは、<、>、&、" などの文字をHTMLエンティティ(<、>、&、")に変換し、Webページで安全に表示できるようにします。URLエンコードは、文字を %XX 形式に変換し、URL内で安全に送信できるようにします。両者は異なる種類の攻撃から保護し、それぞれ異なる文脈で使われます。
HTMLエンティティエンコードとは?
HTMLエンティティエンコード(HTMLエスケープとも呼ばれます)は、HTML内で特別な意味を持つ文字を、ブラウザがマークアップとして解釈せずにそのままの文字として表示するエンティティ参照に変換する処理です。エンコードすべき最も重要な文字は <、>、&、"、' です。
HTMLエンコードを行わないと、<script>alert('XSS')</script> のような文字列はブラウザによってJavaScriptとして実行されてしまいます。エンコードすると <script>alert('XSS')</script> になり、無害なテキストとして表示されます。
HTMLエンティティには2つの書き方があります。&、<、" のような名前付きエンティティと、&、<、" のような数値エンティティです。数値エンティティは文字のUnicodeコードポイントを使うため、あらゆるUnicode文字を表現できます。
<!-- HTMLエンコード前 -->
<p>The formula is: x < y & y > z</p>
<!-- ブラウザは < をタグの開始として解釈してしまう! -->
<!-- HTMLエンコード後 -->
<p>The formula is: x < y & y > z</p>
<!-- ブラウザの表示: x < y & y > z -->
<!-- よく使われるHTMLエンティティ -->
& → & (アンパサンド)
< → < (小なり)
> → > (大なり)
" → " (二重引用符)
' → ' (単一引用符 / アポストロフィ)
→ (改行しない空白)
URLエンコードとは?
URLエンコード(パーセントエンコード)は、URL内で安全に使えない文字を、パーセント記号とそれに続くバイト値を表す2桁の16進数に変換する処理です。RFC 3986 で定義されており、URLがインターネット上で安全に送信できる有効なASCII文字だけで構成されることを保証します。
URLエンコードが必要なのは、URLには特定の文字が区切り文字として機能する厳密な構文があるためです(/、?、&、#、=)。これらの文字がデータ(クエリパラメータの値など)に含まれる場合、構造的な要素として解釈されないようエンコードしなければなりません。
// URLエンコードの例
space → %20
& → %26
= → %3D
? → %3F
# → %23
/ → %2F
+ → %2B
// エンコードされたクエリパラメータを含む完全なURL
https://example.com/search?q=salt%20%26%20pepper
// q の値は "salt & pepper"
比較表
| 項目 | HTMLエンティティエンコード | URLエンコード |
|---|---|---|
| 目的 | HTMLページでの安全な表示 | URLでの安全な送信 |
| 標準規格 | HTML/WHATWG 仕様 | RFC 3986 |
| 形式 | &name; または &#number; |
%XX(16進バイト値) |
| 空白の変換 | (改行しない空白)またはそのまま |
%20 または + |
| & の変換 | & |
%26 |
| < の変換 | < |
%3C |
| 防げるもの | XSS攻撃、壊れたHTML | 壊れたURL、インジェクション攻撃 |
| 文脈 | HTMLドキュメントの本文と属性 | URLのパス、クエリ文字列、フラグメント |
| JS関数 | 組み込みなし(ライブラリやDOM APIを使用) | encodeURIComponent() |
両方が必要になるのはどんなとき?
HTMLエンコードとURLエンコードの両方が必要になる状況はよくあります。最も多いのは、href や src 属性など、HTML属性の中にURLを配置する場合です。まずURLを適切にURLエンコードし、その結果に含まれるHTML上の特殊文字をHTMLエンコードする必要があります。
<!-- ステップ1: クエリパラメータの値をURLエンコードする -->
<!-- 値: "Tom & Jerry" → URLエンコード後: "Tom%20%26%20Jerry" -->
<!-- ステップ2: URLを組み立てる -->
<!-- URL: https://example.com/search?q=Tom%20%26%20Jerry -->
<!-- ステップ3: href属性で使うためにURLをHTMLエンコードする -->
<a href="https://example.com/search?q=Tom%20%26%20Jerry">
Search for Tom & Jerry
</a>
<!-- この場合、% のシーケンスはHTMLエンコードする必要はない -->
<!-- % はHTMLの特殊文字ではないため -->
<!-- ただしURLに区切り文字としての & が含まれる場合は、必ずHTMLエンコードすること: -->
<a href="https://example.com/search?q=cats&sort=name">
Search cats sorted by name
</a>
<!-- & を使わないと、ブラウザが &sort をHTMLエンティティとして解釈するおそれがある -->
React、Vue、Angular などの最新フレームワークでは、テンプレート式やJSXを使うとHTMLエンコードは自動的に処理されます。しかし、動的なデータからURLを組み立てる際には、URLエンコードは依然として明示的に行う必要があります。
// React - HTMLエンコードは自動、URLエンコードは自動ではない
function SearchLink({ query }) {
// URLエンコードは明示的に行う必要がある
const url = '/search?q=' + encodeURIComponent(query);
// JSXではHTMLエンコードは自動
return <a href={url}>Search for {query}</a>;
// React は href とテキストの両方を自動的にHTMLエンコードする
}
XSS対策:どの文脈でどのエンコードを使うか
クロスサイトスクリプティング(XSS)対策では、ユーザー入力が配置される文脈に応じて正しいエンコードを適用する必要があります。ある文脈で誤った種類のエンコードを使っても、まったく保護にはなりません。
- HTML本文の文脈: HTMLエンティティエンコードを使います。これにより、注入された
<script>などのタグがマークアップとして解釈されるのを防ぎます。 - HTML属性の文脈: HTMLエンティティエンコードを使い、属性値は必ず引用符で囲みます。引用符で囲まれていない属性は、HTMLエンコードしていても属性の外へ抜け出されるおそれがあります。
- URLの文脈(href、src): 動的な部分にはURLエンコードを使い、HTMLに配置する場合はURL全体をHTMLエンコードします。
javascript:URLを防ぐため、URLが安全なスキーム(https:)で始まっていることを検証します。 - JavaScriptの文脈: JavaScriptの文字列エスケープ(JSON.stringify または専用のライブラリ)を使います。JavaScriptの文脈では、HTMLエンコードもURLエンコードも十分ではありません。
- CSSの文脈: CSSエスケープを使います。HTMLエンコードやURLエンコードはCSSインジェクションから保護しません。
重要な原則は、入力元ではなく出力先の文脈に合わせてエンコードするということです。1つのユーザー入力でも、レスポンスのどこに現れるかによって異なるエンコードが必要になることがあります。XSS対策を入力検証だけに頼ってはいけません。必ず出力時のエンコードを適用してください。