はじめに
— XSS を防ぐ最も基本で最強の防御線—
XSS(Cross-Site Scripting)は、多くの場合 “信頼してはいけないデータをそのまま HTML に埋め込むこと” が原因で起きる。
この根本的な問題を解決する王道対策が 出力エスケープ(Output Escaping) だ。
出力エスケープを正しく理解すれば、XSS の 70〜80% は防ぐことができる。
Modern Web であっても、出力エスケープは依然として最重要の技術だ。
1. 出力エスケープとは?
出力エスケープとは:
信頼できないデータを HTML・JavaScript・URL などに埋め込むとき、
文脈に応じて危険な文字を安全な表現に変換すること。
これにより、攻撃者が不正な HTML / JavaScript を混入させても、ブラウザが「ただの文字列」として扱うようになる。
例:
悪意ある入力
"><script>alert(1)</script>
エスケープ後の安全な出力
"><script>alert(1)</script>
ブラウザはコードではなく“テキスト”として表示する。
2. 文脈(コンテキスト)ごとの出力エスケープ
XSS 対策で最も重要なのは “どの文脈でデータを埋め込むか”。
文脈により必要なエスケープ方法が違う。
① HTML 本文(テキストコンテキスト)
最も基本形。
| 文字 | エスケープ後 |
|---|---|
< |
< |
> |
> |
" |
" |
' |
' |
& |
& |
例:
<p>{{ userInput }}</p>
→ フレームワークによるデフォルトの HTML-escape が働くべき。
② HTML 属性値
たとえば:
<img src="{{ userInput }}">
属性値では 引用符の閉じを防ぐ必要 がある。
" → "
' → '
属性値コンテキストのミスは Classic XSS の温床。
③ JavaScript 内文字列
次のケースは非常に危険な文脈:
<script>
var name = "{{ userInput }}";
</script>
ここで必要なのは:
| 文字 | エスケープ後 |
|---|---|
" |
\" |
' |
\' |
\ |
\\ |
| 改行 | \n |
JS 文脈を間違うと JS-Injection → XSS になる。
④ URL(リンク・GET パラメータ)
<a href="/search?q={{ userInput }}">
URL 文脈では:
encodeURIComponent(userInput)
が必須。
例えば:
?next=javascript:alert(1)
などの URL ベース XSS を防げる。
⑤ CSS 文脈(rare but dangerous)
CSS にユーザー入力を入れると XSS になるケースがある:
<style>
.box { background: url("{{ userInput }}"); }
</style>
特に expression() や古いブラウザは危険。
→ そもそも CSS にユーザー入力を入れない がベスト。
3. 出力エスケープが防げる攻撃
出力エスケープを適切に行うことで、次の XSS がほぼ完全に防げる。
- Stored XSS
- Reflected XSS
- HTML 属性で起きる XSS
- JS 文字列ベースの XSS
- URL ベース XSS
- イベントハンドラ注入(onclick など)
エスケープだけで XSS の大部分は無効化できる。
4. ❌ しかし出力エスケープにも限界がある
次のケースはエスケープでは防げない:
① DOM-Based XSS(innerHTML など)
element.innerHTML = userInput;
→ Trusted Types や DOMPurify が必要
② リッチテキスト(Markdown / WYSIWYG)
Markdown → HTML の変換時に XSS が混入しやすい。
→ 必ず HTML sanitizer(DOMPurify など) を併用
③ フレームワークの危険操作
React の dangerouslySetInnerHTML
Vue の v-html
→ 名前の通り危険。サニタイズ必須。
④ 人間のミス(最大の問題)
出力エスケープは 開発者のコード量に比例して漏れやすい。
そのため:
- 100% エスケープできている保証がない
- 大規模プロジェクトでは必ず抜けが発生する
→ この弱点を補うのが CSP と Trusted Types。
5. フレームワーク別のエスケープ実装
| フレームワーク | デフォルトの挙動 |
|---|---|
| React | JSX で自動エスケープ(dangerouslySetInnerHTML 以外) |
| Vue | Mustache({{}})は自動エスケープ |
| Angular | 標準で強力な HTML sanitization |
| Django | 変数は自動エスケープ |
| Rails |
<%= %> は自動エスケープ |
→ 自動エスケープを無効化する機能を使った瞬間、XSS の穴が開く。
6. 出力エスケープ + CSP + Trusted Types = 現代の最強構成
出力エスケープだけでは XSS を完全に防ぎきれない。
現代の Web では三つを重ねることが必須:
① 出力エスケープ
→ XSS の 70〜80% を防ぐ基礎
② CSP(script-src 'self')
→ 攻撃者の JS 実行をブロックする第二防御
③ Trusted Types
→ DOM-Based XSS を完全封殺する三段階目の防御
この構成を採用すると、
Web アプリ全体の XSS 攻撃面が ほぼ消滅する。
まとめ:出力エスケープは XSS 対策の本丸
出力エスケープは単なる“変換処理”ではなく、
Web セキュリティの根幹にある考え方。
- HTML、JS、URL などの文脈ごとに
- 正しいエスケープを行い
- 自動エスケープを採用し
- 危険な出力方法を禁じる
これが XSS 防御の基礎となる。
そしてその上に、CSP、Trusted Types、Sanitizer を積み重ねることで、
現代的な“破られないセキュリティレイヤー”が完成する。