初級では「入力を消すのではなく、出力を安全にする」を要点としました。中級では、種類の違いと、なぜ出力側で防ぐのかを整理します。
概要 — まず全体をつかむ
詳細 — 1段階ずつ追う
どう起きる?
XSS は「信頼できない入力が、エスケープされずに HTML・JS・URL へ出力される」ときに成立します。経由する場所で種類が分かれます。
- 反射型 — リクエストに含めた値が、その応答ページにそのまま出る
- 格納型 — 保存された値(コメントなど)が、後で別の利用者の画面に出る
- DOM型 — サーバを介さず、ブラウザ内の JavaScript が値を危険な形で DOM へ書き込む
アニメーション『XSSの流れ(仕込み→表示→実行)』を開く
種類は違っても根本原因は一つ——出力の瞬間に、文脈に合った変換をしていないこと。結果として Cookie(ログイン状態)を盗まれる、勝手に操作される、などの被害が起きます。
この部分をもっと深く(上級)
DOM型XSSは、サーバを介さずブラウザ内だけで完結します。鍵になるのは2つの地点です。
- source(ソース)※1 — 攻撃者が値を注ぎ込める入口。
location.hash・location.search・document.referrer・postMessageで受け取ったデータ・localStorageなど - sink(シンク)※2 — その値が「コードとして解釈されうる出口」。
innerHTML・outerHTML・document.write・eval・setTimeout(文字列渡し)・setAttribute(href/onclickなど)・locationへの代入
sourceからsinkへ、途中でエスケープされないまま値が流れると発火します。ここで大事なのは、サーバの応答HTMLには危険な文字列が一度も現れないことがあるという点です。
アニメーション『XSSの流れ(仕込み→表示→実行)』を開く
だからサーバ側の出力エスケープだけでは素通りします。防御はsinkの手前、つまりブラウザ内で「HTMLとして解釈させない代入(textContent など)」や「サニタイズ」を置くことになります。反射型・格納型が「サーバの出力の問題」なのに対し、DOM型は「ブラウザ内の出力の問題」——同じXSSでも守る場所が違います。
登場人物メモ:
- ※1 source — 攻撃者が制御できる値の入口(
location.hashなどブラウザが持つデータ) - ※2 sink — 値がコードやHTMLとして解釈されうる出口(
innerHTMLやevalなど)
やさしく言うと(初級)
たとえばコメント欄に、攻撃者が「スクリプト入りの文字列」を書き込む。サービスがそれをそのまま画面に表示すると、そのページを見た人のブラウザで、そのスクリプトが実行されてしまいます。
結果、Cookie(ログイン状態)を盗まれる、勝手に操作される、などの被害が起きます。
アニメーション『XSSの流れ(仕込み→表示→実行)』を開く
どう防ぐ?
守りは出力側に置きます。
- 文脈別の出力エスケープ — HTML本文・HTML属性・JS・URL では必要な変換が違う。出す先に合った規則で「ただの文字」に変換する
- CSP(コンテンツセキュリティポリシー) — スクリプトの出所を制限し、許可していないスクリプトの実行をブラウザ側で止める
- HttpOnly Cookie — JavaScript から Cookie を読めなくし、万一スクリプトが動いても盗ませない
ポイントは「入力の混入ではなく、出力の扱いで防ぐ」こと。データは受け取ってよく、出す瞬間に無害化します。
登場人物メモ:
- ※1 出力文脈 — 値を出す先(HTML・属性・JS・URL)。文脈ごとに変換規則が違う
- ※2 CSP — スクリプトの出所を制限し、想定外の実行を止める仕組み
この部分をもっと深く(上級)
XSS対策の核心は「入力の混入ではなく、出力の文脈で防ぐ」ことでした。上級では、その「文脈」を具体化します。値を出す先ごとに、危険な文字も脱出の仕方も違うからです。
- HTML本文 —
<>&を実体参照へ。タグの開始を許さない - HTML属性 — 属性を囲む引用符と、引用符を省いたときの空白などを無害化。属性の脱出を防ぐ
- JavaScript文字列 — 文字列リテラルを抜ける記号(引用符・バックスラッシュ・改行)を処理。値をコードとして生成しないため、そもそもJSにデータを直書きしない設計が上策
- URL — スキームを検証(
javascript:を弾く)し、パーセントエンコード。属性のhref/srcは要注意 - CSS — スタイル値へ生の入力を入れない。
expression相当やurl()経由の悪用を避ける
自由なHTMLをどうしても許したい(リッチテキストなど)場合は、自作エスケープではなく DOMPurify のような実績あるサニタイザで「許可タグ・属性の許可リスト方式」に通します。ここでmXSSまで面倒を見てくれるのが、既製サニタイザを使う理由です。
アニメーション『多層防御(層で守る)』を開く
なぜ入力側で一律に消さないのか。値は保存や再利用の途中では無害で、危険になるのは「特定の文脈へ出す瞬間」だけだからです。入口で削ると本来正しいデータ(a < b のような文字列)まで壊し、しかも別の出口では守れません。詳しくは 入力バリデーション と Webの中のセキュリティ を参照。
やさしく言うと(初級)
- 出力エスケープ — 画面に出すとき、
<や>などを「ただの文字」に変換する。これでスクリプトとして動かない - 文脈に合わせる — HTML・属性・URL・JS の中で、必要なエスケープが違う
- CSP(コンテンツセキュリティポリシー) — そもそも許可していないスクリプトの実行を、ブラウザ側で止める
ポイントは「入力を消すのではなく、出力を安全にする」こと。データは受け取ってよく、出す瞬間に無害化します。
入力検証との役割分担
入力検証は土台ですが、XSS を単独では防げません。検証を通った値でも、出力の文脈しだいで危険になるからです。
- 入力検証 — 想定外を弾く(入口)
- 出力エスケープ — 出す先で無害化する(出口・XSSの主対策)
- CSP/HttpOnly — 破られたときの被害を抑える(多層防御)
この部分をもっと深く(上級)
「危ない語を入口で消せばよい」が効かない理由を、もう一段はっきりさせます。
- 文脈が入口では未定 — 同じ値がHTML本文にも属性にもURLにも出うる。入口では「どの脱出を防げばよいか」が決まらない
- 抜け道が無限 — 大文字小文字・エンコード・全角/半角・コメント挿入・mXSSなど、ブラックリストは必ず抜かれる
- 正当なデータを壊す —
<,&, 引用符は普通の文章にも現れる。入口で削ると機能が壊れる
だから入力検証は土台(想定外を弾く入口)に留め、XSSの主対策は出口の文脈別エスケープに置き、CSP/Trusted Types/HttpOnly Cookie で破られたときの被害を抑える。役割分担が答えです。
⚠️ 破れ方のパターン
- 入力検証だけに頼る — 保存された値が別の画面で出るときに漏れる(格納型XSS)
- エスケープの文脈ミス — HTML用のエスケープを URL や JS にそのまま使う
- innerHTML の乱用 — 受け取った文字列をそのまま HTML として挿入しない
- CSPを免罪符にする — CSP は補助。エスケープの代わりにはならない
この部分をもっと深く(上級)
- DOM型の見落とし — サーバ側だけ守り、
innerHTMLなどブラウザ内のsinkを放置する - サニタイズ済みの再加工 — 一度浄化した文字列を後段で連結・再パースし、mXSSを招く
- CSPを免罪符にする —
unsafe-inlineを残す、広すぎるホスト許可、JSONP経由のバイパス - 文脈の取り違え — HTML用エスケープをJSやURLに流用し、属性やスキームで抜かれる
- テンプレートの自動エスケープ過信 —
innerHTML相当のヘルパや「安全と印を付ける」機能で自動エスケープを外すと穴になる
やさしく言うと(初級)
- 入力検証だけに頼る — 保存された値が別の画面で出るときに漏れる(格納型XSS)
- エスケープの文脈ミス — HTML用のエスケープをURLやJSにそのまま使う
- innerHTML の乱用 — 受け取った文字列をそのままHTMLとして挿入しない
理解度チェック
そのまま解けます(成績は保存されません)。無料アカウントを作ると、学習の記録と進捗の山登りが始まります。
問1. XSS対策として最も本質的なのは?
問2. 反射型・格納型・DOM型のXSSに共通する根本原因は?
問3. 保存された値(コメントなど)が、後で別の利用者の画面に出て発火するXSSはどれ?
問4. サーバを介さず、ブラウザ内のJavaScriptが値を危険な形でDOMへ書き込んで発火するXSSはどれ?
問5. HttpOnly Cookie がXSS対策として果たす役割はどれ?
問6. 役割分担のうち「破られたときの被害を抑える(多層防御)」に当たるのはどれ?
問7. リクエストに含めた値が、その応答ページにそのまま出て発火するXSSの種類を何という(漢字+型)?
問8. スクリプトの出所を制限し、許可していないスクリプトの実行をブラウザ側で止める仕組みを英字3文字で答えてください。