セキュリティマガジン盾 - TATE -

クロスサイトスクリプティング(XSS)を図解で理解!攻撃事例と対策を徹底解説

最終更新日 2026年01月03日 投稿日 2025年12月13日
クロスサイトスクリプティング(XSS)の画像

クロスサイトスクリプティングXSS)は、悪意あるスクリプトを埋め込んでユーザーのブラウザに実行させる代表的なWeb攻撃です。

本記事では、図解でXSSの仕組みや反射型・格納型・DOMベースなどの種類を整理し、Cookie盗難や画面改ざんなどの具体的な攻撃例をわかりやすく紹介します。

さらに、自分のサイトに脆弱性がないか確認する手順と、入力値検証や出力エスケープを中心とした具体的な対策、最後にチェックリスト形式で押さえるべきポイントまで一気に解説します。

目次

クロスサイトスクリプティング(XSS)とは?

クロスサイトスクリプティング(XSS)とは、Webページに埋め込まれた悪意あるscriptやJavaScriptが、利用者のブラウザ上で勝手に実行されてしまう攻撃のことです。

HTMLのコメント欄や検索フォームなどにコードを紛れ込ませる例が多く、Cookieの盗み見や画面改ざん、別サイトでのなりすましやフォージェリにもつながります。

このように、別の Web サイトをまたいで(=クロスして)ユーザーのブラウザに意図しないスクリプトを実行させる脆弱性のことを 「クロスサイトスクリプティング(XSS)」 と呼びます。

では、攻撃者はどのようにしてユーザーのブラウザにスクリプトを実行させているのでしょうか?

次の章では、図解を使って XSS攻撃の仕組みと流れをわかりやすく解説します。

図解で見る!XSS攻撃の仕組みと攻撃の流れ

XSS攻撃の図解

攻撃者が脆弱なページに罠となるscriptを仕掛けると、被害者がそのページを閲覧した際に入力値がそのままHTMLへ反映され、悪意あるJavaScriptが実行されてしまいます

図の例では、被害者のクリックなどの操作をきっかけに攻撃者が埋め込んだ罠スクリプトが呼び出され、Cookie情報などの重要情報が攻撃者へ送られる仕組みになっています。

フォージェリ(なりすまし)などの被害に発展することもあるため、脆弱ページの挙動やscriptが反射される確認方法を理解し、適切な対策を行うことが重要です。

XSS攻撃の種類:反射型・格納型・DOMベースの違いをやさしく解説

XSSにはいくつか種類があり、仕組みや被害が生まれる流れがそれぞれ異なります。

代表的なのが、URLに仕込んだ悪意あるコードがそのまま返される「反射型」、データベースなどに保存された不正なscriptが閲覧時に実行される「格納型(ストアド)」、そしてブラウザ内のJavaScript処理が原因で発生する「DOMベース型」です。

それぞれの種類について見ていきましょう。

反射型XSSとは?検索フォームを悪用した典型的な攻撃例

反射型XSSの図解
反射型XSSとは、検索フォームやURLパラメータに埋め込まれた不正なscriptが、サーバーのレスポンスにそのまま反映されてしまうことで発生する1回性の攻撃です。

攻撃者はSNSやメールで罠URLを拡散し、被害者がアクセスするとブラウザ上でJavaScriptが実行され、Cookieなどの基本情報が盗まれたりフォージェリによる不正操作が行われる例があります。

URLに仕込むだけで実行されてしまうため、Webサイトやメールで不審なリンクを開かないことが重要です。

格納型XSSとは?掲示板やコメント欄が狙われやすい理由

格納型XSS
格納型XSSとは、掲示板やコメント欄など利用者が投稿できる領域に不正なscriptが書き込まれ、それがサーバー側に保存されてしまうことで発生する攻撃です。

保存されたコードは閲覧するたびにHTMLへ挿入され、自動的にJavaScriptが実行されるため、被害者がリンクを踏まなくてもCookieなどの基本情報が盗まれたり、フォージェリによる不正操作が行われる例もあります。

投稿内容がどのように反映されるかという確認方法や、入力値を正しく無害化する対策が重要になります。

DOMベースXSSとは?JavaScriptだけで完結する攻撃パターン

DOMベースXSS
DOMベースXSSとは、サーバーではなくブラウザ内のJavaScript処理が原因で発生するクロスサイトスクリプティングです。

URLのパラメータやハッシュ値などの入力値を、JavaScriptがそのままHTMLに反映してしまうと、不正なscriptが動作し、基本情報の盗難やフォージェリによる不正操作につながる攻撃が成立します。

サーバー側のログには残りにくく、見た目では気付きにくい例が多いため、DOM操作時の確認方法や、入力値を安全に扱う対策が特に重要になります。

XSS3タイプまとめ

項目反射型XSS格納型XSSDOMベースXSS
攻撃の発生源サーバーが攻撃者の入力をそのまま反射サーバーに保存された不正データブラウザ内のJavaScript処理
scriptが混入する場所サーバーのレスポンスHTMLサーバー保存データ → HTMLJavaScriptが操作したDOM
被害が発生するタイミング罠URLを開いた瞬間(1回性)ページ表示のたびに自動実行(持続性)JSがDOMを書き換えた瞬間
被害範囲罠URLを踏んだ利用者のみ閲覧した全ユーザー(広範囲)罠URLを踏んだ利用者のみ
よくある攻撃例検索結果の反射、不正リンク掲示板・コメント欄・プロフィールlocation.hash・innerHTMLの誤使用

XSS対策の基本:入力値検証と出力エスケープを中心に考える

XSS対策は一つの方法だけでは不十分で、入力値の扱い方や出力時の処理、ブラウザ機能の活用など複数の仕組みを組み合わせて安全性を確保します。

ここからは、それぞれの対策が必要となる理由と注意点を順番に解説していきます。

「対策=入力値のチェック+出力のエスケープ」が基本となる理由

XSSを防ぐための基本は、ユーザーが入力した値をそのまま扱わず、危険な文字やscriptが意図せずHTMLとして解釈されないように処理することです。

入力値のチェックでは、そもそも受け付けるべきでない記号や形式を排除し、不正なデータがアプリ内部に入り込むのを防ぎます。一方、出力エスケープは、受け取った値をそのまま画面に表示する際に特殊文字を無害化し、ブラウザがJavaScriptとして実行しないようにする仕組みです。

反射型・格納型・DOMベースのいずれのXSSでも、最終的に危険な文字列がHTMLへ混入することが原因となるため、この2つの対策を組み合わせることが不可欠です。

入力値チェックの基本(ホワイトリスト方式で危険な入力を防ぐ)

XSS対策では、入力段階で「受け付けるべき形式や文字種を制限する」入力値チェックが重要です。文字数や形式、使用できる記号を定めておくことで、不正なデータがアプリ内部へ入り込むリスクを減らせます

入力値チェックで確認すべき5項目

入力値チェックでは、受け付けてよい形式を明確にし、安全なデータだけをホワイトリスト方式で許可することが基本です。

最低限のチェックとしてまずは下記5つの入力値を制限しましょう。

1. 文字数(最小/最大)

異常に長い入力は攻撃の温床になるため、必ず制限しましょう。

function validateLength(str, min, max) { return str.length >= min && str.length <= max; }

このJavaScriptのコードでは、引数の文字列が文字数の最小値と最大値の範囲内であるかチェックしています。

2. 文字種(英数字のみ、日本語可など)

用途に応じて許可する文字を決めましょう。

function isAlphanumeric(str) { return /^[a-zA-Z0-9]+$/.test(str); }

このJavaScriptのコードでは、文字列が許可している文字で構成されているかチェックしています。

3. 形式チェック(メール、URL、日付など)

正規表現やライブラリで形式が正しいか確認しましょう。

function validateEmail(email) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); }

このJavaScriptのコードでは、引数の文字列がメールアドレスの形式になっているかチェックしています。

4. 不要なタグや記号を許可しない

正規表現やライブラリで不要なタグや記号は許可しないようにしましょう。

function isNumeric(str) { return /^[0-9]+$/.test(str); }

このJavaScriptのコードでは、引数の文字列が電話番号の形式になっているかチェックしています。

5. リストにない値を拒否(選択肢の固定)

プルダウンやラジオボタンはサーバー側でも「許可された値かどうか」必ずチェックしましょう。

const allowedRoles = ["admin", "user", "guest"]; function validateRole(role) { return allowedRoles.includes(role); }

このJavaScriptのコードでは、引数の権限が許可された権限であるか判定しています。

入力チェックの注意点

入力値チェックは危険な入力を減らす重要な防御ですが、形式の例外や多言語文字、タグ以外の注入方法などすべてを完全に防ぐことはできません。

そのため XSS では、入力値チェックだけで防ぐのではなく、最終的に画面へ表示する際の「出力エスケープ」と併用することで初めて効果が発揮されます

出力エスケープの基本(HTML・属性値・JavaScript・URLパラメータ)

XSS対策では、同じデータを扱う場合でも表示する場所によって適切なエスケープ方法が異なります。HTML本文に出力するのか、属性値に埋め込むのか、JavaScript内部で扱うのか、URLパラメータとして利用するのかによって、必要な無害化処理が変わるためです。

ここでは、それぞれの無害化処理について例を交えて説明します。

HTML本文に出力

<!-- 悪い例:そのまま出力するとXSS --> <div id="msg">{{ userInput }}</div> <!-- 良い例:< > & " などをエスケープ --> <div id="msg">{{ escapeHTML(userInput) }}</div>

1つ目のコードでは、{{ userInput }} をそのまま HTML に埋め込んでいるため、ユーザーが <script> やイベント属性を入力した場合もそのまま反映されてしまいます。
このように 入力値を直接 HTML に挿入する実装 は、もっとも典型的な XSS 脆弱性の原因となります

2つ目のコードでは、ユーザ定義のサニタイズ関数であるescapeHTML(userInput) を使うことで < や > を安全な文字列(<、> など)に変換し、ブラウザが意図せず HTML として解釈しないようにしています
このように HTML本文へ出力する前に必ずエスケープを行うこと が、XSS 対策の基本です。

ここで使用している userInput は、ユーザーがフォームなどに入力した値を表す仮の変数です。

function escapeHTML(str) { return str .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;"); }

このJavaScriptのユーザー定義関数である escapeHTML() は、HTML の解釈に悪影響を与える特殊文字をすべて安全なエンティティへ変換する簡易的なサニタイズ処理です。
特に <・>・"・& は攻撃で多用されるため、これらをエスケープするだけでも XSS リスクは大きく下がります。

ここで使用している escapeHTML() は、XSS の仕組みを理解するための簡易的なサニタイズ関数です。実際の開発では、フレームワークの自動エスケープ機能や DOMPurify などの専門ライブラリを利用することが推奨されます。

HTML属性値に埋め込み

<!-- 悪い例 --> <img src="{{ userInput }}"> <!-- 良い例:属性専用のエスケープ --> <img src="{{ escapeAttribute(userInput) }}">

悪い例では、ユーザー入力 userInput をそのまま img タグの src 属性に埋め込んでいます。一見画像URLを指定しているだけに見えますが、属性値内では "(ダブルクオート)を閉じられてしまうと、その後に任意の属性を挿入されてしまいます

良い例では、escapeAttribute(userInput) によってユーザー入力を属性値用に無害化しています。属性値では " や '、<、> といった危険な文字を適切なエスケープ文字(" や < など)に変換する必要があります。これにより、攻撃者が不正な値を渡しても属性値の途中で文字列を終了させたり、新しい属性を挿入することができなくなり、安全にHTMLへ挿入できるようになります。

function escapeAttribute(str) { return String(str) .replace(/&/g, "&amp;") .replace(/"/g, "&quot;") .replace(/'/g, "&#39;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;"); }

このJavaScriptのユーザー定義関数であるescapeAttribute() は、ユーザー入力を HTML の「属性値」に安全に埋め込むための 無害化(エスケープ)処理 を行う関数です。属性値では ", ', <, >, & といった文字が特に危険で、適切に変換しないと属性が途中で途切れたり、イベントハンドラを挿入されて XSS が発生します。
この関数では、String(str) で確実に文字列として扱ったうえで、危険文字をそれぞれ HTML エンティティへ置き換えて安全な形にします。属性にユーザー入力を埋め込む場合は、このような専用のエスケープ処理が必須となります。

JavaScript内部に埋め込み

<!-- 悪い例:JS中に直接埋め込む --> <script> var name = "{{ userInput }}"; // XSSの温床 </script> <!-- 良い例:安全にJSONエンコードして扱う --> <script> var name = {{ JSON.stringify(userInput) }}; </script>

悪い例では、ユーザー入力 userInput を JavaScript の変数へそのまま埋め込んでいます。これは XSS の典型的な脆弱箇所で、入力値に "; alert(1); // のような文字列が含まれていると、そのまま JavaScript として実行されてしまいます。

良い例では、JSON.stringify(userInput) を使って、ユーザー入力を安全な文字列として JavaScript に渡しています。JSON.stringify は危険な記号を自動的にエスケープし、値を「実行可能なコード」ではなく「ただの文字列」として扱うため、スクリプトが勝手に動作する心配がありません。これにより、悪意ある入力が含まれても JavaScript として解釈されず、XSS 攻撃を防ぐことができます。

URLパラメータ

// 悪い例 const url = "/search?q=" + userInput; // 良い例 const url = "/search?q=" + encodeURIComponent(userInput);

悪い例では、ユーザー入力 userInput をそのまま URL のパラメータに連結しています。この方法は一見問題なさそうに見えますが、実際には非常に危険です。
ユーザーが &page=2 や ?x=1# のような文字列を入力すると、本来意図していない追加パラメータを付与されたり、URL の構造を途中で切り替えられてしまう可能性があります。また、<script> などを仕込まれると、遷移先で XSS が発生するケースもあります。URL に外部入力を直接結合することは、安全性の観点から避けるべき実装です。

良い例では、ブラウザ標準関数であるencodeURIComponent(userInput) を使用し、ユーザー入力を URL のパラメータとして安全に利用できる形へエンコードしています
この関数は、URL 内で特別な意味を持つ ? や &、=、#、<、> といった記号をすべて安全なエスケープ文字へ置き換えてくれます。そのため、攻撃者が不正なクエリやスクリプトを含む文字列を入力しても、意図した URL の構造が壊れることがなく、XSS や URL 改ざんを未然に防ぐことができます。

最後にチェック!XSS対策チェックリスト

クロスサイトスクリプティング(XSS)とは何か、そしてその基本的な対策方法について本記事ではまとめました。

もし自分の運営しているWebサイトが対策できていないかもしれないと不安になった方は、下記のチェックリストを確認してみてください。

  • 入力値に異常な文字・形式が入らないかチェックしているか
  • HTML・属性・JS・URL の「出力先」に応じたエスケープをしているか
  • innerHTML を不用意に使っていないか
  • JSON.stringify で JS変数を安全に扱っているか
  • encodeURIComponent で URL を安全にしているか
  • コメント欄・掲示板など「保存される入力」が特に危険だと理解しているか

また、今回紹介しきれなかった入力チェックやエスケープ以外のXSS攻撃対策についての記事も投稿していますので、気になる方は是非チェックしてみてください。

参考記事:CSP(Content Security Policy)とは?XSS対策に欠かせない理由と注意点