はじめに

クロスサイトスクリプティング、通称XSSは、古くから存在するWeb脆弱性です。
しかし、XSSを「反射型・格納型・DOM型の3種類」とだけ覚えると、現代のWebアプリケーションを正しく評価できません。
SPA、WebSocket、ブラウザー内テンプレート、SVG、WebView、ブラウザー拡張機能などが普及したことで、攻撃経路は大きく広がりました。
現代のXSSには、次のようなものがあります。
Mutation XSS
Blind XSS
Script Gadget
DOM Clobbering
Prototype Pollutionから到達するXSS
Client-Side Template Injection
postMessageやWebSocketを経由するDOM XSS
ブラウザーや拡張機能そのものを狙うUniversal XSS
本記事では、XSSの黎明期から2026年時点の分類までを整理します。
単なる用語集ではありません。
「何が起きるのか」「自分のシステムに関係するか」「なぜ発生するか」「どう確認するか」「どう直すか」「どう再発を防ぐか」の順で解説します。
結論・要点
XSSは、次の4つの軸を分けて考えると理解しやすくなります。
| 分類軸 | 主な分類 |
|---|---|
| データが保存されるか | Reflected、Stored |
| どこで危険な処理が行われるか | Server XSS、Client XSS |
| どの仕組みを悪用するか | DOM、mXSS、Script Gadget、DOM Clobberingなど |
| いつ、誰の画面で実行されるか | Blind、Second-order、Self-XSSなど |
最も重要な基本分類は、次のマトリクスです。
データの扱われ方
Reflected Stored
┌──────────────┬──────────────┐
サーバー側 │ Reflected │ Stored │
でHTML生成 │ Server XSS │ Server XSS │
├──────────────┼──────────────┤
ブラウザー側 │ Reflected │ Stored │
でDOM更新 │ Client XSS │ Client XSS │
└──────────────┴──────────────┘
DOM-based XSSは、主にClient XSSに含まれる
OWASPも、単純な3分類だけでなく、「StoredかReflectedか」と「ServerかClientか」を組み合わせて整理しています。DOM-based XSSはClient XSSの一部として位置づけられます。
また、2026年時点でもXSSは過去の脆弱性ではありません。
現行のOWASP Top 10:2025では、XSSは独立項目ではなく「A05:2025 Injection」に含まれます。同資料では、CWE-79に関連するCVEが3万件を超えるとされています。これは、XSSが現在も極めて頻繁に報告される脆弱性であることを示します。
この記事で分かること
この記事では、次の内容を理解できます。
反射型、格納型、DOM型の違い
Server XSSとClient XSSの違い
Blind XSSとSecond-order XSSの違い
Mutation XSSがサニタイザーをすり抜ける仕組み
SVG、MathML、Markdown、ファイルアップロードの危険性
DOM Clobbering、Prototype Pollution、Script Gadgetとの関係
WebSocket、postMessage、API経由のXSS
Universal XSS、拡張機能、WebViewのXSS
XSSと似ているが別物の攻撃
DjangoやJavaScript環境での確認方法
CSP、Trusted Types、WAF、IPS、EDRの役割
修正後の動作確認と再発防止
対象読者・前提環境
本記事は、次の読者を対象としています。
Web開発を学び始めた人
Django、Flask、PHP、Java、Node.jsなどを使う開発者
JavaScript、TypeScript、React、Vue、Angularを使う開発者
WebSocketやSPAを実装している人
CMS、チャット、管理画面、SOC画面を運用している人
脆弱性診断やセキュリティレビューを担当する人
コード例は、主にHTML、JavaScript、Djangoを使用します。
攻撃用の外部送信コードやCookie窃取コードは掲載しません。安全な確認用文字列を使って説明します。
用語と全体像
XSSとは
XSSとは、信頼できないデータが、ブラウザーによってHTMLやJavaScriptなどの実行可能な内容として解釈される脆弱性です。
たとえると、受付へ渡したメモの内容を表示するだけのはずが、メモに書かれた「管理画面を開く」という命令まで実行してしまう状態です。
正式には、次の流れで発生します。
信頼できない入力
↓
Sourceから取得
↓
文字列の連結・変換・保存
↓
危険なSinkへ渡される
↓
HTML・JavaScript・URLなどとして解析
↓
被害者のブラウザーで実行
MITREでは、XSSをCWE-79「Webページ生成時における入力の不適切な無害化」として整理しています。
Sourceとは
Sourceは、信頼できないデータの入口です。
代表例は次のとおりです。
location.href
location.search
location.hash
document.referrer
window.name
postMessageのevent.data
WebSocketの受信メッセージ
localStorage
sessionStorage
APIレスポンス
Cookie
データベース
HTTPヘッダー
Sourceから取得しただけでは、必ずしもXSSになりません。
その値が危険なSinkへ到達すると問題になります。
Sinkとは
Sinkは、文字列をHTML、JavaScript、URLなどとして解釈する可能性がある処理です。
代表例は次のとおりです。
element.innerHTML = value;
element.outerHTML = value;
element.insertAdjacentHTML("beforeend", value);
document.write(value);
eval(value);
new Function(value);
iframe.srcdoc = value;
次のコードは危険です。
const message = location.hash.slice(1);
document.querySelector("#result").innerHTML = message;
次のコードは、文字列表示だけが目的なら比較的安全です。
const message = location.hash.slice(1);
document.querySelector("#result").textContent = message;
OWASPも、postMessageなどで受け取った値をeval()やinnerHTMLへ渡さず、表示だけならtextContentを使うよう推奨しています。
「String-to-Code Conversion」の正しい理解
XSSの根本原因を「文字列からコードへの変換」と説明する考え方は有用です。
ただし、ブラウザーがすべての文字列を無差別にコードへ変換するわけではありません。
正確には、次の条件がそろうと問題になります。
攻撃者が制御できる文字列
+
コードやマークアップを解釈するSink
+
不適切なエンコードまたはサニタイズ
=
XSS
たとえば、textContentは通常、文字列を文字として表示します。
一方、innerHTMLは文字列をHTMLとして解析します。
したがって、問題の中心は「Webのすべてのプロトコルが文字列を自動実行すること」ではありません。
信頼できない文字列を、実行能力を持つAPIへ渡す設計が問題です。
XSSの歴史
1999年:Cross-Site Scriptingという名称が生まれる
OWASPのアプリケーションセキュリティ史では、1999年にMicrosoftのエンジニアが「Cross Site Scripting」という用語を作ったと整理されています。
当時は、別サイトの内容やスクリプトを信頼されたサイトへ注入し、サイト間の境界を悪用する攻撃が中心でした。
現在のXSSは、必ずしも複数サイトをまたぎません。
OWASPも、現在では名称の意味が広がり、さまざまなコンテンツ注入を含む言葉になったと説明しています。
2003年:最初のOWASP Top 10
OWASP Top 10の最初の版は2003年に公開されました。XSSは、Webアプリケーションで優先的に対処すべき問題として広く認識されるようになりました。
なお、「2000年にXSSが公式な学術・実務体系へ組み込まれた」という明確な区切りは、今回確認した一次資料では裏付けられませんでした。
そのため、本記事では2000年を正式な分類確立年とは断定しません。
2005年:DOM-based XSSが整理される
2005年、Amit Klein氏が「DOM Based Cross Site Scripting or XSS of the Third Kind」を公開しました。
これにより、サーバーのHTTPレスポンスへ攻撃文字列が直接含まれなくても、ブラウザー内部のDOM処理だけでXSSが成立することが明確に整理されました。
ただし、これを「XSSの主軸がサーバーからクライアントへ完全に移行した」と表現するのは正確ではありません。
現在もStored Server XSSやReflected Server XSSは多数発生しています。
正確には、次のように理解します。
サーバー側XSSが消えたのではなく、クライアント側に新しい攻撃面が追加された。
2012年:CSP 1.0
CSPは、ページが読み込みまたは実行できるリソースを、HTTPヘッダーなどで制限する仕組みです。
OWASPの歴史資料では、CSP 1.0の公開を2012年としています。
CSPは重要な防御ですが、エスケープやサニタイズの代わりではありません。
2013年:Mutation XSSの体系化
2013年の研究では、innerHTMLなどによるDOM変換によって、安全に見えた文字列が危険な構造へ変異するMutation XSSが体系化されました。
同研究では、当時の主要ブラウザーファミリーや複数のサニタイザーに影響する攻撃経路が示されました。
2017年:Script Gadgetの研究
Googleの研究者らは、Webアプリケーションに元から存在する正規JavaScriptを、攻撃用の部品として再利用するScript Gadgetを発表しました。
攻撃者が直接スクリプトタグを挿入しなくても、安全に見えるHTMLを既存コードに処理させ、最終的にコード実行へ到達する手法です。
2025~2026年:型で危険なSinkを制御する時代へ
2026年時点では、CSP Level 3とTrusted Typesの標準化が進められています。
CSP Level 3は2026年5月5日時点でWorking Draftです。Trusted Typesも2026年2月24日時点でWorking Draftであり、最終的なW3C Recommendationではありません。
Trusted Typesは、innerHTMLなどの危険なSinkへ、普通の文字列を直接渡せないようにする考え方です。
従来の防御が「入力を安全に加工する」ことを中心としていたのに対し、Trusted Typesは「危険なAPIへ未確認の文字列を渡させない」方向へ進化しています。
基本となるXSSの種類
1. Reflected XSS:反射型XSS

反射型XSSは、リクエストに含まれた入力が、直後のレスポンスへ反射されるXSSです。
攻撃用に加工されたURL
↓
Webサーバー
↓
入力を含むHTMLレスポンス
↓
被害者のブラウザーで実行
代表的な入力場所は次のとおりです。
検索キーワード
エラーメッセージ
URLパラメーター
HTTPヘッダー
POSTデータ
リダイレクト先
プレビュー機能
特徴
通常はデータベースへ保存されない
被害者にURLなどを開かせる必要がある
フィッシングと組み合わせられやすい
Server XSSの場合とClient XSSの場合がある
MITREでは、反射型XSSをType 1として整理しています。
2. Stored XSS:格納型XSS

格納型XSSは、攻撃文字列がデータベースやログへ保存され、後から閲覧した利用者のブラウザーで実行されるXSSです。
攻撃者がデータを登録
↓
DB・ログ・キャッシュへ保存
↓
利用者または管理者が閲覧
↓
ブラウザーで実行
保存先の例は次のとおりです。
コメント
掲示板
プロフィール
チャット
商品レビュー
問い合わせ
ファイル名
User-Agent
Referer
アクセスログ
メール本文
CMS記事
特徴
被害者が特別なURLを開かなくても発生する
多数の利用者へ継続的に影響する可能性がある
管理画面で発火すると影響が大きくなりやすい
Server XSSの場合とClient XSSの場合がある
3. DOM-based XSS

DOM-based XSSは、ブラウザー内のJavaScriptが信頼できない値を読み取り、危険なDOM操作へ渡すことで発生します。
const value = location.hash.slice(1);
document.querySelector("#output").innerHTML = value;
この例では次のように整理できます。
| 項目 | 内容 |
|---|---|
| Source | location.hash |
| Sink | innerHTML |
| 発生場所 | ブラウザー |
| サーバーレスポンスの変更 | 必須ではない |
OWASPは、DOM-based XSSをClient XSSの一部として整理しています。
Server XSSとClient XSS

Server XSS
サーバーが信頼できないデータを、危険な状態でHTMLレスポンスへ埋め込むXSSです。
入力
↓
Django・PHP・Javaなどのサーバー処理
↓
危険なHTMLレスポンス
↓
ブラウザーで実行
Server XSSには次が含まれます。
Reflected Server XSS
Stored Server XSS
Client XSS
ブラウザー内のJavaScriptが、信頼できないデータを危険なSinkへ渡すXSSです。
入力
↓
API・URL・WebSocket・Storage
↓
JavaScript
↓
innerHTMLなど
↓
ブラウザーで実行
Client XSSには次が含まれます。
Reflected Client XSS
Stored Client XSS
DOM-based XSS
「格納型かDOM型か」という二者択一は誤り
APIから取得した保存済みコメントを、JavaScriptがinnerHTMLへ表示するケースを考えます。
コメントをDBへ保存
↓
APIがJSONで返す
↓
JavaScriptが取得
↓
innerHTMLへ代入
この脆弱性は、次の両方に該当します。
Stored XSS
Client XSSまたはDOM XSS
XSSの分類は一つだけ選ぶものではありません。
複数の分類軸を組み合わせて表現します。
実行コンテキストによる分類
HTML本文コンテキスト
<p>{{ user_input }}</p>
必要な対策は、HTML本文用の出力エンコードです。
HTML属性コンテキスト
<input value="{{ user_input }}">
属性値は必ず引用符で囲みます。
次のように引用符がない構造は危険です。
<div class={{ user_input }}>
Django公式ドキュメントも、属性値を引用符で囲まない場合は、自動エスケープだけでは不正な属性追加を防げない例を示しています。
JavaScriptコンテキスト
<script>
const username = "{{ user_input }}";
</script>
HTMLエスケープだけでは、安全を保証できません。
可能な限り、信頼できない値をJavaScriptコードへ直接埋め込まない設計にします。
Djangoでは、構造化データを渡す場合にjson_scriptなどの専用機能を検討します。
URLコンテキスト
<a href="{{ user_url }}">リンク</a>
URLエンコードだけでは不十分です。
次の検証も必要です。
許可するスキーム
許可するホスト
相対URLか絶対URLか
リダイレクト先の制限
javascript:などの危険なスキーム
CSSコンテキスト
<style>
.profile {
background-image: url("{{ value }}");
}
</style>
CSSコンテキストも独自の解析規則を持ちます。
信頼できない文字列をCSSへ直接埋め込む設計は、可能な限り避けます。
発火条件による派生分類
Blind XSS

Blind XSSは、攻撃者自身には実行画面が見えず、後から管理者やサポート担当者が入力を閲覧したときに実行されるXSSです。
代表例は次のとおりです。
問い合わせ管理画面
SOCダッシュボード
ログビューアー
チケット管理画面
メール管理画面
不正アクセス調査画面
User-Agent一覧
Blind XSSの多くはStored XSSです。
攻撃者が細工したUser-Agentを送信
↓
アクセスログへ保存
↓
SOC担当者がログ画面を開く
↓
管理者権限の画面で実行
セキュリティ製品が検知したXSS文字列を、管理画面で未エスケープ表示するケースには特に注意が必要です。
Second-order XSS

Second-order XSSは、入力地点と実行地点が離れているXSSです。
プロフィールへ登録
↓
登録画面では問題なし
↓
別の管理画面で再利用
↓
別コンテキストで実行
Stored XSSと近い意味で使われることがあります。
Second-orderという言葉は、特に次の点を強調します。
入力時点では発火しない
別の処理を経由する
別画面や別システムで発火する
Delayed XSS

Delayed XSSは、バッチ処理、承認処理、キャッシュ更新、帳票生成などを経由して、時間差で発火するXSSです。
独立した標準分類というより、Stored XSSやSecond-order XSSの実行タイミングを表す実務上の用語です。
Self-XSS

Self-XSSは、利用者自身にJavaScriptを貼り付けさせるソーシャルエンジニアリングです。
代表的な誘導は次のとおりです。
開発者ツールへ貼り付ける
「無料アイテムを受け取れる」と説明する
「アカウント認証用コード」と偽る
ブラウザーのコンソールへ入力させる
Webアプリケーション側に注入脆弱性が存在しない場合、通常のXSSとは区別されます。
パーサーやHTML構造を悪用するXSS
Mutation XSS:mXSS

Mutation XSSは、サニタイズ時には安全に見えた文字列が、ブラウザーの解析やDOM変換によって危険な構造へ変化するXSSです。
攻撃者の入力
↓
サニタイザーでは安全と判定
↓
ブラウザーへ渡される
↓
innerHTMLなどで再解析
↓
DOM構造が変異
↓
実行可能な内容になる
2013年のmXSS研究では、入力文字列がフィルター通過時には実行可能な形でなくても、innerHTML処理による変異後にXSSとして成立する仕組みが示されました。
原因
HTMLパーサーのエラー補正
名前空間の切り替え
HTML、SVG、MathMLの混在
不正な入れ子
サニタイザーとブラウザーの解析差
サニタイズ後の再シリアライズ
innerHTMLによる再解析
対策
正規表現だけでHTMLを除去しない
保守されているHTMLサニタイザーを使う
サニタイズ後のHTMLを再加工しない
innerHTMLの利用を減らすライブラリを更新する
SVGやMathMLが不要なら許可しない
SVG XSS

SVGは画像形式として扱われますが、XMLベースの文書です。
利用方法によっては、イベント属性、リンク、外部参照、foreignObjectなどを持てます。
危険になりやすい構成は次のとおりです。
利用者がアップロードしたSVGを同一オリジンで配信
インラインSVGへ利用者入力を挿入
SVGを汎用HTMLサニタイザーだけで処理
SVGをHTML文書として誤配信
SVG内のURLやイベント属性を許可
画像ファイルだから安全とは限りません。
MathML XSS

MathMLは、Webページで数式を表現するマークアップです。
HTML、SVG、MathMLの間では名前空間が切り替わります。
この境界は解析規則が複雑であり、mXSSやサニタイザーバイパスの研究対象になります。
MathMLが不要なサービスでは、許可リストから除外する方が安全です。
Polyglot XSS

Polyglot XSSは、複数のコンテキストで解釈できるように作られた攻撃文字列です。
対象となるコンテキストの例は次のとおりです。
HTML本文
HTML属性
JavaScript文字列
URL
SVG
コメント
テンプレート
Polyglotは独立した脆弱性の原因ではありません。
複数のフィルターや解析環境へ対応するための入力設計手法です。
JavaScriptやDOM構造を悪用する攻撃
DOM Clobbering

DOM Clobberingは、HTML要素のidやnameが、windowやdocumentのプロパティとして参照される仕組みを悪用します。
const target = window.redirectTo || "/profile/";
location.assign(target);
攻撃者がredirectToという名前のHTML要素を挿入できると、JavaScriptが想定していた変数をHTML要素で覆い隠す可能性があります。
OWASPはDOM Clobberingを、HTMLだけを利用するコード再利用型の注入攻撃として説明しています。スクリプトタグを直接注入できなくても、変数やブラウザーAPIを上書きし、XSSへ到達する場合があります。
対策
windowやdocumentを変数置き場にしない変数を明示的に宣言する
グローバル変数を減らす
idとnameを含めてHTMLをサニタイズするURLを型と許可リストで検証する
DOM要素と文字列を区別する
CSPを補助対策として利用する
Prototype PollutionからXSSへ到達する攻撃

Prototype Pollutionは、JavaScriptのプロトタイプを書き換え、多数のオブジェクトへ意図しないプロパティを継承させる問題です。
攻撃者入力
↓
Object.prototypeなどを汚染
↓
ライブラリが汚染値を設定として読む
↓
危険なDOM処理へ渡す
↓
XSS
Prototype Pollution単体は、必ずしもXSSではありません。
汚染された値をinnerHTML、スクリプトURL、テンプレート処理などへ渡すGadgetがある場合、XSSへ発展します。
OWASPは、Prototype Pollutionによってオブジェクトやプロパティが操作され、権限昇格やコード実行などへつながる可能性を説明しています。
対策
__proto__、constructor、prototypeを入力キーとして許可しない深いマージを行うライブラリを更新する
スキーマ検証を実施する
辞書用途では
Mapを検討する必要に応じて
Object.create(null)を使う危険なDOM Sinkへ到達するGadgetを調査する
Script Gadget XSS

Script Gadgetは、アプリケーションに元から存在する正規JavaScriptを、攻撃者が実行部品として悪用する攻撃です。
攻撃者が安全に見えるHTMLを注入
↓
正規JavaScriptが特定の要素を検索
↓
属性やテキストを読み取る
↓
危険な処理へ渡す
↓
コード実行
攻撃者が直接script要素を挿入しなくても成立する場合があります。
Googleの研究では、既存コードに含まれるScript Gadgetが、HTMLサニタイズやCSPなどの緩和策を回避する可能性が示されました。
テンプレートを悪用するXSS
Client-Side Template Injection
Client-Side Template Injection、CSTIは、ブラウザー側テンプレートエンジンが利用者入力を文字列ではなく式として評価する問題です。
<div>
{{ user_input }}
</div>
使用するテンプレートエンジンによっては、テンプレート式からJavaScriptのオブジェクトや関数へ到達できる場合があります。
XSSとの関係
CSTIそのものは、テンプレート式の注入です。
最終的にブラウザーでJavaScript実行へ到達した場合、XSSとして扱われます。
Server-Side Template Injectionとの違い
Server-Side Template Injection、SSTIは、サーバー側テンプレートエンジンが攻撃者の式を評価する問題です。
CSTI
└─ ブラウザー側テンプレートで評価
└─ XSSへ到達する場合がある
SSTI
└─ サーバー側テンプレートで評価
└─ サーバー上のコード実行へ到達する場合がある
SSTIとXSSは別の脆弱性です。
通信経路や保存場所に関係するXSS
postMessage XSS
window.postMessage()で受信したデータを、検証せずにDOMへ入れるとDOM XSSになります。
危険な例です。
window.addEventListener("message", (event) => {
document.querySelector("#message").innerHTML = event.data;
});
改善例です。
const ALLOWED_ORIGIN = "https://trusted.example";
window.addEventListener("message", (event) => {
if (event.origin !== ALLOWED_ORIGIN) {
return;
}
if (typeof event.data !== "string") {
return;
}
document.querySelector("#message").textContent = event.data;
});
OWASPは、event.originを完全一致で確認し、受信データをコードとして評価しないよう求めています。
WebSocket XSS

WebSocketの受信データをinnerHTMLへ入れると、DOM XSSまたはStored Client XSSになります。
攻撃者がチャットへ投稿
↓
WebSocketサーバーが配信
↓
他の利用者が受信
↓
innerHTMLで描画
↓
XSS
WebSocket自体がXSSを起こすわけではありません。
受信データの描画方法が原因です。
安全な例です。
socket.addEventListener("message", (event) => {
const item = document.createElement("li");
item.textContent = event.data;
document.querySelector("#messages").appendChild(item);
});
APIレスポンス由来のXSS

JSON形式で受信したデータも、安全とは限りません。
危険な例です。
const response = await fetch("/api/profile/");
const profile = await response.json();
document.querySelector("#name").innerHTML = profile.display_name;
JSONは転送形式です。
JSONであることは、HTMLとして安全であることを意味しません。
localStorage・sessionStorage XSS

Web Storageへ保存された値を、後から危険なSinkへ渡すとXSSになります。
URLパラメーター
↓
localStorageへ保存
↓
別画面で読み込み
↓
innerHTMLへ代入
これは、Stored Client XSSまたはクライアント側Second-order XSSと整理できます。
実行基盤に関係するXSS
Universal XSS:UXSS

Universal XSSは、特定のWebサイトの実装ミスではなく、次のようなプラットフォーム側の脆弱性によって発生します。
ブラウザー本体
Webレンダリングエンジン
ブラウザー拡張機能
組み込みWebView
PDFビューアー
共通コンポーネント
通常のXSSでは、脆弱なWebサイトのオリジンでスクリプトが動きます。
UXSSでは、ブラウザーの境界を破り、別オリジンの情報へ到達する可能性があります。
Webサイト側のエスケープだけでは完全に防げません。
ブラウザーやOS、拡張機能、WebViewを更新する必要があります。
Browser Extension XSS

ブラウザー拡張機能が、Webページや外部データを未検証でHTMLへ描画すると、拡張機能側でXSSが発生します。
拡張機能は次の権限を持つ場合があります。
タブ情報の取得
ページ内容の読み書き
拡張機能Storageへのアクセス
特定サイトへの通信
ネイティブアプリとの連携
通常のWebページより影響が大きくなる可能性があります。
WebView XSS

スマートフォンアプリやデスクトップアプリ内のWebViewで発生するXSSです。
特に危険なのは、JavaScriptからネイティブ機能を呼び出せるBridgeがある構成です。
WebView内でXSS
↓
JavaScript Bridgeへアクセス
↓
ネイティブ機能を呼び出す
↓
端末側の処理へ影響
対策には、Webコンテンツの安全化だけでなく、公開するネイティブAPIの最小化が必要です。
ファイル・Markdown・ログ画面のXSS
File Upload XSS

利用者がアップロードしたファイルを、同一オリジンでHTMLとして表示するとXSSになる場合があります。
注意が必要な形式は次のとおりです。
HTML
XHTML
SVG
XML
MHTML
不正なContent-Typeを持つファイル
HTMLを含む複合形式
利用者が指定したファイル名
Django公式ドキュメントでは、有効なPNGヘッダーの後ろへHTMLを付加したファイルが画像検証を通過し、Webサーバー設定によってはHTMLとして表示される例が説明されています。アップロードファイルを別のトップレベルドメインまたはセカンドレベルドメインから配信する対策も推奨されています。
Markdown XSS

Markdownだから安全とは限りません。
次の構成ではXSSが起こる可能性があります。
Markdown内の生HTMLを許可している
危険なURLスキームを許可している
Markdown変換後のHTMLを未検証で表示している
Mermaidや数式レンダラーへ未検証データを渡している
サニタイズ後のHTMLを再加工している
プラグインが独自HTMLを生成する
推奨する流れは次のとおりです。
Markdown入力
↓
Markdownパーサー
↓
HTMLへ変換
↓
保守されたHTMLサニタイザー
↓
表示
Log Viewer XSS

アクセスログや攻撃ログには、攻撃者が制御できる値が多数含まれます。
URI
User-Agent
Referer
Host
Cookie
HTTPヘッダー
エラーメッセージ
リクエスト本文
これらをSOC画面や管理画面でHTMLとして表示すると、Blind Stored XSSが発生します。
攻撃文字列を表示する場合は、コードブロック風の見た目であっても、DOM上では必ずテキストとして描画します。
XSSと間違えやすい攻撃
| 攻撃 | XSSか | 違い |
|---|---|---|
| HTML Injection | 条件による | スクリプト実行へ到達しなければXSSではない |
| CSS Injection | 原則別 | CSSの表示操作や情報取得を狙う |
| Dangling Markup | 別 | 未完のタグで後続情報を外部送信する |
| XSSI | 別 | 他オリジンのスクリプト形式データを読み取る |
| CSRF | 別 | 被害者に意図しない操作を実行させる |
| SSTI | 別 | サーバーテンプレートで式を実行する |
| Prototype Pollution | 単体では別 | GadgetがあるとXSSへ発展する |
| DOM Clobbering | 単体では別の場合あり | DOM要素で変数やAPIを覆い隠す |
| CSV Injection | 別 | 表計算ソフトで数式を実行する |
| Open Redirect | 別 | 外部サイトへ不正に誘導する |
| Clickjacking | 別 | 透明な画面などでクリックを誘導する |
| XS-Leaks | 別 | ブラウザーの副作用から情報を推測する |
自分のシステムに関係するか
次の機能が一つでもある場合、XSS対策が必要です。
コメントや掲示板
チャット
WebSocket
ユーザープロフィール
CMS
Markdown
リッチテキストエディター
ファイルアップロード
SVG表示
管理画面
ログビューアー
SOCダッシュボード
APIからの動的表示
postMessageブラウザーStorage
WebView
JavaScriptテンプレート
利用者指定URL
外部ウィジェット
HTMLメール表示
Mermaidや数式レンダリング
特に、次の処理がある場合は優先的に確認します。
innerHTML
outerHTML
insertAdjacentHTML
document.write
eval
new Function
srcdoc
dangerouslySetInnerHTML
v-html
unsafeHTML
safe
mark_safe
autoescape off
原因
XSSの原因は、単なる「入力チェック不足」だけではありません。
主な原因は次のとおりです。
原因1:出力先を考えずに同じエスケープを使う
HTML本文、属性、JavaScript、URL、CSSは、解析規則が異なります。
OWASPも、コンテキストごとに異なる出力エンコードが必要だと説明しています。
原因2:文字列表示にinnerHTMLを使う
単純な文章表示にinnerHTMLを使う必要はありません。
textContentやDOM生成APIを使います。
原因3:フレームワークの安全機能を無効化する
代表例は次のとおりです。
Djangoの
safeDjangoの
mark_safe{% autoescape off %}Reactの
dangerouslySetInnerHTMLVueの
v-htmlAngularの
bypassSecurityTrustAs*Litの
unsafeHTML
OWASPも、現代的なフレームワークには自動エスケープがある一方、安全機能を迂回するAPIがXSSの原因になると注意しています。
原因4:サニタイズ後に再加工する
入力
↓
サニタイズ
↓
文字列置換・連結
↓
innerHTMLへ再投入
この流れでは、サニタイズ後に再び危険な構造が生まれる可能性があります。
原因5:JSONやWebSocketなら安全だと思い込む
JSON、WebSocket、APIは配送方法です。
受信した値をHTMLとして解釈すればXSSになります。
原因6:管理画面を信頼している
管理画面が表示するデータも、元をたどれば攻撃者が入力した値かもしれません。
管理者しか見ない画面ほど、Blind XSSの影響が大きくなります。
確認方法
1. JavaScriptの危険なSinkを検索する
目的
フロントエンドコードから、XSSにつながりやすいDOM APIを探します。
実行場所
プロジェクトのルートディレクトリで実行します。
rg -n --hidden \
-g '!node_modules/**' \
-g '!vendor/**' \
-g '!static/vendor/**' \
'innerHTML|outerHTML|insertAdjacentHTML|document\.write|eval\(|new Function|srcdoc'
正常例
検索結果なし
または、固定文字列だけを扱う安全な利用箇所だけが表示されます。
異常例
static/js/chat.js:84:messageList.innerHTML += event.data;
static/js/profile.js:41:target.innerHTML = response.display_name;
判断方法
検索結果があるだけで脆弱性とは断定できません。
右辺のデータが次の入力源から来ていないか確認します。
URL
API
WebSocket
Storage
フォーム入力
DB
外部サービス
2. Djangoのエスケープ解除箇所を検索する
目的
Djangoの自動エスケープを迂回している箇所を探します。
実行場所
Djangoプロジェクトのルートで実行します。
rg -n --hidden \
-g '!venv/**' \
-g '!static/vendor/**' \
'\|safe|mark_safe|autoescape off|SafeString'
正常例
検索結果なし
または、固定HTMLだけを扱う限定的な処理としてレビュー済みです。
異常例
templates/chat/message.html:18:{{ message.body|safe }}
chat/services.py:92:return mark_safe(user_message)
判断方法
利用者入力、APIレスポンス、DB保存値をsafeやmark_safeへ渡している場合は、優先的に修正します。
Django公式ドキュメントも、safe、mark_safe、autoescape offの利用には特に注意するよう説明しています。
3. WebSocket受信値の描画を検索する
目的
WebSocketメッセージが直接HTMLへ渡されていないか確認します。
実行場所
フロントエンドのソースディレクトリで実行します。
rg -n \
'event\.data|message\.data|socket\.onmessage|addEventListener\(["'\'']message' \
static templates frontend
検索結果の周辺に、次の処理がないか確認します。
innerHTML
insertAdjacentHTML
document.write
eval
正常例
item.textContent = event.data;
異常例
chatBox.insertAdjacentHTML("beforeend", event.data);
判断方法
受信データがHTMLとして必要ない場合は、textContentへ変更します。
4. CSPヘッダーを確認する
目的
CSPが設定されているか確認します。
実行場所
インターネットへ接続できる管理端末で実行します。
curl -sI https://example.com/ |
grep -iE 'content-security-policy|x-content-type-options'
正常例
content-security-policy: script-src 'nonce-...' 'strict-dynamic'; object-src 'none'; base-uri 'none'
x-content-type-options: nosniff
異常例
出力なし
または、次のような広すぎる設定です。
content-security-policy: script-src * 'unsafe-inline' 'unsafe-eval'
判断方法
CSPがないことは、XSSが存在する証拠ではありません。
CSPがあることも、XSSが存在しない証拠にはなりません。
CSPは補助防御です。
5. Djangoのデプロイ設定を確認する
目的
Djangoの基本的な本番向けセキュリティ設定を確認します。
実行場所
Djangoコンテナまたは仮想環境内で実行します。
python manage.py check --deploy
正常例
System check identified no issues
異常例
security.W004
security.W008
security.W012
判断方法
このコマンドはXSSを網羅的に検出しません。
HTTPS、Cookie、HSTSなどの基本設定を確認する補助コマンドです。
6. 安全な確認文字列を使用する
最初から実行コードを使わず、次のような文字列を入力します。
XSS_TEST_<>&"'_2026
確認項目は次のとおりです。
画面上に文字として表示されるか
HTML要素として増えていないか
DOM Inspectorで意図しない属性が生まれていないか
API、DB、管理画面を経由しても文字のままか
別ユーザーの画面でも同じか
本番環境では、許可なく能動的な攻撃ペイロードを使用しないでください。
対処方法
1. データはデータとして表示する
危険な例です。
output.innerHTML = userInput;
改善例です。
output.textContent = userInput;
2. DOM APIで要素を組み立てる
危険な例です。
list.innerHTML += `<li>${username}</li>`;
改善例です。
const item = document.createElement("li");
item.textContent = username;
list.appendChild(item);
3. Djangoの自動エスケープを維持する
通常の表示です。
<p>{{ message.body }}</p>
利用者入力へ安易に次を使用しないでください。
<p>{{ message.body|safe }}</p>
Djangoテンプレートは多くのXSSを自動エスケープしますが、属性の引用符不足やsafeの使用など、保護範囲外の構成があります。
4. HTMLを許可する場合は専用サニタイザーを使う
コメント装飾やリッチテキストなど、HTMLが必要な場合は次の方式を取ります。
入力
↓
許可する要素・属性を定義
↓
保守されたHTMLサニタイザー
↓
サニタイズ済みHTMLとして保存または表示
ブラックリスト方式ではなく、許可リスト方式を基本にします。
不要なら次を許可しません。
scriptイベント属性
styleSVG
MathML
iframeobjectembedsrcdoc任意URLスキーム
5. CSPを導入する
基本例です。
Content-Security-Policy:
script-src 'nonce-RANDOM_VALUE' 'strict-dynamic';
object-src 'none';
base-uri 'none';
導入時は、最初から強制せず、次の順で進める方法があります。
Content-Security-Policy-Report-Only
↓
違反レポートを収集
↓
正規スクリプトを整理
↓
nonce対応
↓
強制モードへ移行
CSP Level 3は2026年5月時点でWorking Draftです。CSPはページが取得・実行できるリソースや、セキュリティ関連の方針を制御します。
6. Trusted Typesを段階導入する
Trusted Typesは、危険なSinkが通常の文字列を受け付けないようにします。
概念的な設定例です。
Content-Security-Policy:
require-trusted-types-for 'script';
trusted-types app-html;
イメージは次のとおりです。
通常の文字列
└─ innerHTMLへ代入
└─ 拒否
アプリケーションが定義したポリシー
↓
サニタイズ
↓
TrustedHTML
└─ innerHTMLへ代入
└─ 許可
Trusted Typesはサニタイザーではありません。
危険なSinkへ渡す値を、アプリケーションが定義したポリシー経由に限定する仕組みです。W3C仕様でも、攻撃者が制御する入力を強力なWeb APIへ渡すことで生じる脆弱性を減らす目的が説明されています。
7. アップロードファイルを別オリジンで配信する
推奨構成です。
アプリケーション
https://example.com
アップロードファイル
https://usercontent-example.com
次の構成だけでは十分でない場合があります。
https://uploads.example.com
Cookieやドメイン設定によっては、サブドメイン間の影響が残るためです。
Django公式ドキュメントも、利用者アップロードを異なるトップレベルまたはセカンドレベルドメインから配信する方法を推奨しています。
8. Cookie設定で影響を抑える
次の設定はXSSそのものを防ぎません。
HttpOnly
Secure
SameSite
ただし、被害を抑える補助対策になります。
HttpOnlyを設定しても、XSSが被害者のブラウザー上でAPIを呼び出す可能性は残ります。
WAF・IPS・EDRによる補助対策
WAF
WAFは次の用途で役立ちます。
既知のXSSパターンの検知
緊急時の仮想パッチ
異常なエンコードの検知
大量スキャンの遮断
特定パラメーターへの制限
ただし、次の攻撃は苦手です。
DOM-only XSS
localStorageをSourceとするXSS
Script Gadget
DOM Clobbering
Prototype Pollution経由
mXSS
正常なHTMLに見える入力
アプリケーション固有テンプレート式
OWASPも、WAFやHTTPインターセプターへの依存は、DOM-based XSSを防げない場合があると説明しています。
IPS
ネットワークIPSは、既知の攻撃パターンや異常通信を検知できます。
しかし、URLフラグメントのようにサーバーへ送信されないデータだけで成立するDOM XSSは、ネットワーク上で観測できない場合があります。
IPSは補助対策です。
EDR
EDRは、XSSそのものよりも、XSS後の不審動作を検知できる可能性があります。
例は次のとおりです。
ブラウザーから不審なプロセスが起動
不審なファイルがダウンロードされる
WebViewからネイティブ機能へ異常アクセス
ブラウザー拡張機能が異常動作
認証情報を狙う不審な通信
一般的なWeb上のDOM XSSを、EDRだけで根本的に防ぐことはできません。
CSPレポートとSIEM
CSP違反レポートを収集し、SIEMやSOCへ送ることで、次を検知できる場合があります。
未許可スクリプトの実行試行
インラインスクリプトの発生
不正な外部ドメインへの接続
予期しないフレーム
デプロイ後のCSP崩れ
ただし、レポートにはノイズも含まれるため、相関分析と除外設定が必要です。
動作確認
修正後は、入力画面だけでなく、データが到達するすべての画面を確認します。
確認ルート
入力画面
↓
DB
↓
一覧画面
↓
詳細画面
↓
管理画面
↓
API
↓
WebSocket
↓
ログ画面
↓
メール・帳票
確認項目
| 確認項目 | 正常な状態 |
|---|---|
| HTML本文 | 特殊文字が文字として表示される |
| 属性値 | 引用符が壊れない |
| JavaScript | 利用者入力をコードへ直接埋め込まない |
| API | JSON値をDOMへ安全に描画する |
| WebSocket | textContentなどで表示する |
| 管理画面 | ログやUser-Agentをテキスト表示する |
| Markdown | 変換後にサニタイズする |
| SVG | 不要なら拒否する |
| アップロード | 別オリジンから安全に配信する |
| CSP | 正規機能を壊さず違反を遮断する |
| Trusted Types | 未承認文字列のSink投入が拒否される |
再発防止
コーディング規約
次をプロジェクトの禁止または要レビュー項目にします。
innerHTML
outerHTML
insertAdjacentHTML
document.write
eval
new Function
srcdoc
dangerouslySetInnerHTML
v-html
unsafeHTML
|safe
mark_safe
autoescape off
完全禁止できない場合は、利用理由、入力元、サニタイズ方法、レビュー担当者を記録します。
CIで危険APIを検索する
CIへ静的な検索ルールを追加します。
rg -n \
'innerHTML|outerHTML|insertAdjacentHTML|document\.write|eval\(|new Function' \
static frontend templates
単純な文字列検索には誤検知があります。
最終的には、ESLint、Semgrep、CodeQL、SASTなどと組み合わせます。
テストケースを固定する
次のデータをテストフィクスチャへ追加します。
XSS_TEST_<>&"'_ATTRIBUTE
XSS_TEST_MARKDOWN
XSS_TEST_WEBSOCKET
XSS_TEST_FILENAME
XSS_TEST_USER_AGENT
入力から管理画面まで、文字として表示されることを自動テストします。
脅威モデリングへSourceとSinkを追加する
機能ごとに次を記録します。
| 項目 | 内容 |
|---|---|
| Source | URL、API、DB、WebSocketなど |
| 変換処理 | Markdown、テンプレート、JSONなど |
| Sink | innerHTML、URL属性など |
| コンテキスト | HTML、属性、JavaScript、URL |
| 防御 | エンコード、サニタイズ、CSP |
| 表示者 | 一般ユーザー、管理者、SOC担当者 |
依存ライブラリを更新する
特に次のライブラリを継続的に確認します。
HTMLサニタイザー
Markdownパーサー
テンプレートエンジン
DOM操作ライブラリ
リッチテキストエディター
SVG処理ライブラリ
深いマージを行うJavaScriptライブラリ
WebView
ブラウザー拡張機能
mXSSやPrototype Pollutionは、ライブラリ内部の解析やオブジェクト操作が原因になる場合があります。
注意点・よくある誤解
誤解1:HTMLタグを禁止すれば防げる
不十分です。
属性、URL、SVG、テンプレート、DOM Clobberingなど、script要素を使わない経路があります。
誤解2:入力時にエスケープすればよい
出力先が確定していない段階での一律エスケープは、二重エスケープや別コンテキストへの不適合を起こします。
基本は、出力直前にコンテキストに合わせて処理します。
誤解3:JSONなら安全
JSONをinnerHTMLへ渡せばXSSになります。
JSONは安全化処理ではありません。
誤解4:HttpOnlyでXSSを防げる
HttpOnlyは、JavaScriptからCookie値を直接読み取ることを制限します。
XSS自体は防ぎません。
誤解5:CSPがあれば修正不要
CSPは防御の一層です。
OWASPも、CSPだけへ依存することをアンチパターンとして扱っています。
誤解6:WAFが全部止める
WAFから見えないDOM Sourceや、正常なHTMLを再利用するScript Gadgetがあります。
アプリケーションコードの修正が必要です。
誤解7:DOM XSSは反射型・格納型と別の三択
DOM XSSは発生場所を示す分類です。
Stored DOM XSSやReflected Client XSSも存在します。
誤解8:Web技術はサーバーXSSからクライアントXSSへ完全移行した
Server XSSは現在も存在します。
現代のWebでは、サーバー側に加えてクライアント側、ブラウザー拡張機能、WebViewなどの攻撃面が増えたと理解するのが正確です。
XSS分類の全体図
XSS
├─ 基本分類
│ ├─ Reflected XSS
│ └─ Stored XSS
│
├─ 発生場所
│ ├─ Server XSS
│ │ ├─ Reflected Server XSS
│ │ └─ Stored Server XSS
│ └─ Client XSS
│ ├─ Reflected Client XSS
│ ├─ Stored Client XSS
│ └─ DOM-based XSS
│
├─ 発火条件
│ ├─ Blind XSS
│ ├─ Second-order XSS
│ ├─ Delayed XSS
│ └─ Self-XSS
│
├─ パーサー・マークアップ
│ ├─ Mutation XSS
│ ├─ SVG XSS
│ ├─ MathML XSS
│ └─ Polyglot XSS
│
├─ JavaScript構造
│ ├─ DOM Clobbering to XSS
│ ├─ Prototype Pollution to XSS
│ ├─ Script Gadget XSS
│ └─ Client-Side Template Injection
│
├─ データ経路
│ ├─ postMessage XSS
│ ├─ WebSocket XSS
│ ├─ API-based XSS
│ └─ Web Storage XSS
│
├─ 媒体
│ ├─ File Upload XSS
│ ├─ Markdown XSS
│ ├─ Log Viewer XSS
│ └─ HTML Email XSS
│
└─ プラットフォーム
├─ Universal XSS
├─ Browser Extension XSS
└─ WebView XSS
まとめ
XSSは、「悪意あるscriptタグを入力される脆弱性」ではありません。
本質は、次の一点にあります。
信頼できないデータが、ブラウザーの実行能力を持つコンテキストへ到達すること。
初期のXSSは、主にサーバーが生成するHTMLの問題として扱われていました。
その後、DOM-based XSS、mXSS、Script Gadget、DOM Clobbering、Prototype Pollution、WebViewなど、ブラウザー内部やプラットフォーム階層まで攻撃面が広がりました。
ただし、古いXSSが新しいXSSへ置き換わったわけではありません。
現在は、古典的な反射型・格納型に、クライアント側とプラットフォーム側の攻撃経路が積み重なっています。
実務では、次の順序で対策します。
自動エスケープを維持する
コンテキストに合わせて出力エンコードする
innerHTMLなどの危険なSinkを減らすHTMLが必要な場合だけ専用サニタイザーを使う
API、WebSocket、Storageの値も信頼しない
管理画面やログ画面もテキスト表示する
CSPを防御の追加層として導入する
Trusted Typesで危険なSinkを制御する
アップロードファイルを別オリジンで配信する
CI、コードレビュー、自動テストで再発を防ぐ
XSSを「3種類の暗記」で終わらせず、Source、Sink、コンテキスト、実行場所、保存性を組み合わせて分析することが重要です。
FAQ
Q1. 最低限覚えるべきXSSは何ですか
最初は次の3つを覚えます。
Reflected XSS
Stored XSS
DOM-based XSS
その後、Server/Clientの分類を重ねると理解しやすくなります。
Q2. DOM XSSはサーバーのログに残りますか
必ずしも残りません。
location.hashは通常、HTTPリクエストとしてサーバーへ送信されません。
そのため、URLフラグメントだけで成立するDOM XSSは、サーバーやWAFから見えない場合があります。
Q3. Djangoを使えばXSSは起こりませんか
起こる可能性があります。
Djangoテンプレートは多くの入力を自動エスケープしますが、次の構成には注意が必要です。
safemark_safeautoescape off引用符のない属性
JavaScriptへ直接埋め込む
innerHTMLMarkdown
利用者アップロード
WebSocket
管理画面でのログ表示
Django公式ドキュメントも、自動保護には限界があると説明しています。
Q4. Reactなら安全ですか
通常の文字列描画はエスケープされます。
ただし、次の処理では注意が必要です。
dangerouslySetInnerHTML外部URL
古い依存ライブラリ
DOM APIの直接利用
MarkdownやHTMLレンダラー
Script Gadget
OWASPも、フレームワークの安全機能を迂回するAPIを注意点として挙げています。
Q5. XSSの修正は入力チェックだけで十分ですか
十分ではありません。
入力検証、出力エンコード、安全なDOM API、HTMLサニタイズ、CSP、Trusted Typesを組み合わせます。
Q6. CSPでXSSを完全に防げますか
完全ではありません。
設定不備、許可済みスクリプト、Script Gadget、ブラウザー差異などがあります。
CSPは根本修正の代わりではありません。
Q7. Trusted Typesを入れればServer XSSも防げますか
Trusted Typesは、主にクライアント側の危険なInjection Sinkを制御します。
W3C仕様では、サーバー生成マークアップへの注入を直接防ぐことは非目標として示されています。Server XSSには、テンプレートの自動エスケープやコンテキスト別の出力処理が必要です。
Q8. SOC画面に攻撃文字列を表示しても大丈夫ですか
未エスケープ表示は危険です。
URI、User-Agent、Referer、攻撃ペイロードは、必ずプレーンテキストとして表示してください。
参考情報
OWASP Cross Site Scripting Prevention Cheat Sheet。XSSのコンテキスト別防御、フレームワークの注意点、CSPだけへ依存する問題を整理しています。
OWASP DOM based XSS Prevention Cheat Sheet。DOM XSSのSource、Sink、実行コンテキストを整理しています。
OWASP Types of Cross-Site Scripting。Stored/ReflectedとServer/Clientの分類を整理しています。
W3C Trusted Types Working Draft。Injection Sinkを型付き値に制限する仕組みを定義しています。
W3C Content Security Policy Level 3。CSP Level 3の2026年時点の状態を確認できます。
Google ResearchのScript Gadget研究。正規JavaScriptを再利用する攻撃を説明しています。
Django 5.2公式セキュリティドキュメント。自動エスケープの保護範囲とアップロードファイルの注意点を説明しています。
2026年7月30日追記:サイト全体のXSS監査と改善
この記事の公開前、記事機能だけでなく、サイト全体を対象にクロスサイトスクリプティング対策を再点検しました。サーバー側テンプレート、ブラウザー側DOM操作、リッチHTML、画面へ埋め込むJSON、リアルタイム通信、管理・通知系の表示経路を横断して確認しています。
今回実施した主な改善
- リッチHTMLを扱う機能では、許可リスト型のサニタイズを保存時と表示時の両方へ適用しました。
- HTML内へJSONを渡す処理を、script要素を安全に閉じ込める共通シリアライズ方式へ変更しました。
- APIレスポンスからDOMを組み立てる画面では、文字列をHTMLとして連結せず、値のエスケープとURL制限を行うよう見直しました。
- 過去に保存されたデータも、安全化してから表示されるよう互換対策を追加しました。
確認したXSSの観点
反射型、格納型、DOM型に加え、Second-order、Blind、SVG・MathML、危険なURLスキーム、イベント属性、iframe/srcdoc、CSS、JSON埋め込み、WebSocketなどの経路を確認しました。安全確認には実害のない検証用データを使用し、外部送信や認証情報取得を行うコードは使用していません。
防御は一つに依存しない
今回の改善では、出力コンテキストに応じたエスケープ、HTMLサニタイズ、安全なDOM API、セキュリティヘッダー、回帰テストを組み合わせています。CSPやWAFだけに依存せず、信頼できないデータが実行可能なSinkへ到達しない設計を優先しています。
なお、この追記では悪用可能な入力例、非公開URL、内部構成、検知回避につながる具体的手順は掲載していません。セキュリティ対策は継続作業であり、機能追加時にもSourceとSinkの追跡、依存ライブラリ更新、回帰テストを続けます。
コメント(0件)
まだコメントはありません。最初のコメントを投稿してください!