HTMLエンティティ エンコーダー/デコーダー
特殊文字をHTMLエンティティに変換、またはその逆。
一般的なHTMLエンティティ
| 文字 | エンティティ | 数値 | 説明 |
|---|---|---|---|
| & | & | & | アンパサンド |
| < | < | < | より小さい |
| > | > | > | より大きい |
| " | " | " | 二重引用符 |
| ' | ' | ' | アポストロフィ |
| |   | 改行されない空白 | |
| © | © | © | 著作権 |
| ® | ® | ® | 登録商標 |
| ™ | ™ | ™ | 商標 |
| € | € | € | ユーロ記号 |
なぜHTMLエンティティを使うのか?
<、>、&などの文字はHTMLで特別な意味を持ちます。HTMLにそれらを文字通り含めると、ブラウザはそれらをコンテンツではなくコードとして解釈します。これらの文字をエンティティにエンコードすると、レンダリングの問題とクロスサイトスクリプティング(XSS)の脆弱性を回避できます。
よくある質問
名前付きエンティティと数値エンティティの違いは?
名前付きエンティティは説明的な名前(&、©)を使用し、数値エンティティはUnicodeコードポイント(&、©)を使用します。両方とも同じように表示されます。名前付きエンティティはより読みやすく、数値エンティティはあらゆるUnicode文字に対して機能します。
すべての特殊文字をエンコードする必要がありますか?
最低限、HTMLコンテンツと属性値で&、<、>、"をエンコードする必要があります。最新のブラウザは、ドキュメントがUTF-8エンコーディングを使用していれば、ほとんどの他の文字をネイティブに処理します。
3つの形式、1つの文字
HTMLの「エンティティ」、正式には文字参照:1つの文字を通常のASCII文字列で表現するエスケープシーケンスだ。HTML Living Standardは3つの具体的な構文形式を定義している:
- 名前付き:WHATWGの名前付き文字参照テーブルの名前を
&の後に付け、セミコロンで終端する。例:©の©。 - 10進数値:
&#の後にUnicodeコードポイントを10進数で付け、セミコロンで終端する。例:©。 - 16進数値:
&#xの後にコードポイントを16進数で付け、セミコロンで終端する。例:©。
3つの形式はレンダリング時に互換性がある:©、©、©はすべて文字©を生成する。これらはすべてUnicodeコードポイントU+00A9に解決されるからだ。どれを選ぶかはブラウザの動作ではなく、ソースコードの可読性の問題だ。16進数は公開されているUnicodeチャート(U+XXXX表記を使用)に一致しやすい。そのため♥は公式表記U+2665に♥より近い。数値参照はアストラルプレーンの絵文字を含むあらゆるUnicode文字に対応している:😀は😀(U+1F600 GRINNING FACE)としてレンダリングされる。
エンティティが存在する理由
歴史的・実用的な3つの理由:
- HTMLで構文的な意味を持つ文字をエスケープするため。パーサーは特定のASCII文字を制御記号として使用する。
<はタグを開き、>はタグを閉じ、&は参照を導入し、引用符は属性値を区切る。これらをリテラルテキストとして表示したい場合はエスケープしなければならない。 - ドキュメントのエンコーディングで使用できない文字を表現するため。UTF-8がウェブで普及する前(2010年頃に50%を超えたばかり)、ほとんどのHTMLはUS-ASCII、ISO-8859-1、またはWindows-1252として提供されていた。これらのシングルバイトエンコーディングでは©、€、≈、αのような文字をリテラルバイトで表現できなかった。
©、€、≈を記述することがそれらのコードポイントに到達する唯一の方法だった。 - 不可視または曖昧な文字の意図を明示するため。UTF-8ページでも、リテラルの非改行スペース(U+00A0)は通常のスペースと見た目が同一だ。
と記述することでソースを読む人に意図が明確になる。
W3Cは現在、エンティティよりも可能な限りリテラルのUnicode文字を使用するよう推奨している、「アクセシビリティと可読性のため」。エンティティは5つの必須エスケープと、本当に不可視または曖昧な文字については引き続き有用だ。
ビッグファイブ
信頼できないコンテンツをHTMLに挿入する際に必ずエスケープしなければならない5つの文字は<、>、&、"、'だ。OWASPのクロスサイトスクリプティング防止チートシートは最低限必要なエスケープセットとしてこれらを列挙している:
| 文字 | 名前付き | 10進数 | 16進数 |
|---|---|---|---|
| < | < | < | < |
| > | > | > | > |
| & | & | & | & |
| " | " | " | " |
| ' | ' / ' | ' | ' |
経験則:信頼できないテキストをHTML出力に置く際は、必ず最初にこの5つの文字をエスケープする。これを怠ることが、蓄積型および反射型XSS脆弱性の圧倒的多数の根本原因だ。
アポストロフィの罠
'はHTML 4の一部ではない。元々はXML 1.0のみで定義され、XHTML 1.0に継承された。バージョン9(2011年リリース)以前のInternet Explorerは'としてレンダリングすることを拒否し、リテラルテキスト'を表示していた。このエンティティはHTML5で特別に追加され、現在はすべてのモダンブラウザで安全だが、最大限のクロスブラウザ・クロスSpec互換性のため、OWASPとほとんどのエンタープライズサニタイゼーションライブラリは単一引用符をエスケープする際、特にセキュリティ重要コードでは'を使用し'は避けることを推奨している。
エンコードするときとしないとき
エンコードの決定は、テキストが何を含んでいるかではなく、出力のどこに置かれるかに依存する。これはHTMLセキュリティで最も誤解されているポイントだ。OWASPのガイダンスはコンテキストを区別している:
- HTML要素コンテンツ:
< > & "をエスケープする(念のため'も)。 - HTML属性値:同じものに加え引用符文字もエスケープする;常に引用符付き属性を使用する。
- JavaScriptコンテキスト:HTMLエスケープではなくJavaScriptエスケープを使用する:
\xHHまたは\uHHHH。 - CSSコンテキスト:CSSエスケープを使用する:スペースに続く
\HH。 - URL / URIパラメーターコンテキスト:HTMLエンコーディングではなくパーセントエンコーディング(
%HH)を使用する。
各コンテキストには固有のエスケープルールがある。それらを混在させること自体が脆弱性になる。例えば<を%3Cにパーセントエンコードしても、HTML要素コンテキストではXSSを防げない。そこでは%3Cがリテラルテキスト%3Cに過ぎないからだ。
二重エンコーディングを避ける。よくあるバグは、データをシステムに読み込むとき、保存するとき、読み出すとき、レンダリングするときの各段階でエスケープすることだ。その結果:ユーザーが5 < 10と入力し、データベースが5 &lt; 10を保存し、ページがオリジナルの代わりに5 < 10をレンダリングする。規律は:生のUnicodeを保存し、出力の瞬間に特定のコンテキストのために一度だけエンコードすることだ。
HTMLエンコーディングとURLエンコーディング
異なる2つのコンテキストに対する異なる2つのエスケープシステム、常に混同されている:
| HTMLエンティティ | URL / パーセント | |
|---|---|---|
| 標準 | HTML Living Standard | RFC 3986 |
| フォーマット | &name; または &#NN; | %HH(16進バイト) |
| コンテキスト | HTMLマークアップ、要素本体、属性 | URL、クエリ文字列、フォームエンコードされたリクエストボディ |
| スペース | (非改行スペース)、プレーンスペース不可 | %20 または + |
| JS関数 | −(パーサーが処理) | encodeURIComponent() / encodeURI() |
HTML属性値内のURLには両方のエスケープが重ねられる:まずURL不正文字にパーセントエンコーディング、次に結果のURLの& < > "にHTMLエンコーディング。これがhref属性内のクエリ文字列アンパサンドがHTMLシリアライゼーションで&になる理由だ。
簡単な歴史
HTML 2.0(RFC 1866、1995年)はISO Latin 1の約50の名前付きエンティティとともにSGMLのエンティティ機構を継承した。HTML 3.2(W3C、1997年1月)は数学記号エンティティを追加した。HTML 4.01(W3C、1999年12月)は3つのエンティティセット(Latin-1、Special、Symbol)を合計252の名前付きエンティティで確定した。これが古いチュートリアルに今も見られる「252」という数字の出所だ。HTML5 / WHATWG(Living Standard、進行中)はテーブルを2,000以上のエントリーに大幅拡張し、主にMathMLとより広いUnicodeセットをカバーしている。XML 1.0(1998年)はビッグファイブだけ(< > & " ')の独自の最小セットを定義している。その最小セットが'の起源だ。
その他の質問
現代のコードでは実際に何を使うべきか?
本番コードは通常エンティティエンコーディングを手動で実装せず、ライブラリを呼び出す。クライアントサイドHTMLサニタイゼーションにはDOMPurify。Pythonの標準ライブラリのhtml.escape()。PHPのhtmlspecialchars()。Goのhtml/template(デフォルトで自動エスケープ有効)。JavaのOWASP Java Encoder。Reactでは<div>{userInput}</div>と記述すると自動的にエスケープされる;エスケープハッチのdangerouslySetInnerHTMLは日常的な使用を抑止するために命名されている。このようなスタンドアローンエンコーダーはサニティチェック/デバッグツールとして有用だが、これらのライブラリの代替ではない。
メールテンプレート内のタグはどうか?
古いメールクライアント(特にOutlook)はエンコードされていない&を不正な属性と解釈し、周囲のマークアップを削除することがある。HTMLメール開発者はすべての特殊文字を防御的にエンティティエンコードすることを学ぶ。コンテンツを保存前に書き換えるフォーラムのBBCodeスタイルシステムも同様で、ラウンドトリップが予期しないリテラルエンティティを導入することがある。
JavaScriptのtextContentとinnerHTMLの違いは?
バニラJavaScriptにおける最も重要なXSS防止ルール:element.textContent = userInputを使い、element.innerHTML = userInputは避ける。textContentの設定は文字列をリテラルテキストとして書き込み、ブラウザが内部でエスケープをすべて処理する。innerHTMLの設定は文字列をHTMLとしてパースし、含まれる<script>タグやイベントハンドラー属性を実行する。マークアップが本当に必要な場合は、まずDOMPurifyのようなライブラリでサニタイズする。
エンコーダーは絵文字を処理できるか?
はい、数値参照を使って処理できる。絵文字には名前付きエンティティがなく、すべて数値形式を使用する。😀は😀としてレンダリングされ、❤️は赤いハート❤️(ハートコードポイントと絵文字プレゼンテーションセレクター)としてレンダリングされる。ブラウザはUTF-16サロゲートペアの変換を内部で処理する;サロゲートの半分を手動で記述すべきではない。
何かサーバーに送信されるか?
いいえ。エンコードとデコードは完全にブラウザ内でJavaScriptで実行される純粋関数の文字列変換だ。入力内容は何もアップロードされない;ロードされた後はオフラインで動作する。クラウドベースのエンコーダーがテストペイロードをラウンドトリップする場合、テストサイトが侵害されると自体がXSSベクターになりかねないため、これは重要だ。