クロスサイトスクリプティング(XSS):攻撃ベクトルと防御策
XSSが依然としてWebアプリケーションを悩ませる理由
クロスサイトスクリプティング(XSS)は、最も蔓延しているWeb脆弱性の一つであり続けています。広く認知されているにもかかわらず、OWASP Top 10に常に登場します。根本的な問題は、アプリケーションがユーザー入力を信頼し、適切な処理を行わずにレンダリングすることです。攻撃者は悪意のあるスクリプトを注入し、それが被害者のブラウザで実行されることで、セッションハイジャック、データ窃取、改ざんなどが発生します。
この記事では、XSSの攻撃ベクトルを分解し、今日から実装できる実用的な防御策を提供します。
XSSとは何か、どのように機能するのか?
XSSは、アプリケーションが検証やエンコーディングを行わずに信頼できないデータをWebページに含めることで発生します。ブラウザは、注入されたスクリプトを正当なサイトの一部であるかのように実行します。スクリプトは脆弱なサイトのコンテキストで実行されるため、Cookieやローカルストレージにアクセスし、ユーザーに代わってリクエストを行うことができます。
クエリを反映する単純な検索ページを考えてみましょう:
<?php echo 'You searched for: ' . $_GET['q']; ?>
攻撃者が search.php?q=<script>alert('XSS')</script> のようなURLを作成すると、被害者がリンクを訪問したときにスクリプトが実行されます。
XSSの主な3つのタイプ
1. 反射型XSS
悪意のあるスクリプトがリクエスト(例:URLパラメータ)の一部であり、応答に即座に反映されます。被害者はリンクをクリックするかフォームを送信するよう騙される必要があります。これはフィッシングキャンペーンでよく使用されます。
2. 格納型XSS
スクリプトがサーバー上(例:データベース、コメントフィールド、ユーザープロファイル)に永続的に保存されます。影響を受けるページを訪れるすべての訪問者がスクリプトを実行します。格納型XSSは、ページを閲覧する以外の操作を必要としないため、より危険です。
3. DOMベースXSS
脆弱性は、信頼できないソース(location.hashなど)からデータを読み取り、安全に処理せずにDOMに書き込むクライアント側のJavaScriptに存在します。サーバーは悪意のあるペイロードを決して見ないかもしれません。
document.getElementById('output').innerHTML = location.hash.substring(1);
ハッシュに <img src=x> が含まれている場合、スクリプトが実行されます。
一般的な攻撃ベクトル
- HTML内のエスケープされていないユーザー入力: HTMLコンテンツ、属性、またはJavaScriptに直接注入します。
- 危険なシンクの不適切な使用:
innerHTML、document.write、eval、文字列引数を持つsetTimeoutなどの関数。 - URLベースの注入:
javascript:URIを使用してhref、src、またはstyle属性を操作します。 - サードパーティコンポーネント: 信頼できないデータをレンダリングする脆弱なライブラリやウィジェット。
XSSに対する防御策
1. 出力エンコーディング(コンテキストに応じたエスケープ)
HTML、属性、JavaScript、CSS、またはURLでレンダリングする前に、すべての信頼できないデータをエンコードします。コンテキストに適したエンコーディングを使用します:
- HTMLエンティティエンコーディング:
&、<、>、"、'をエンティティに変換します。 - JavaScriptエンコーディング: 非英数字文字をUnicodeにエスケープします。
- URLエンコーディング: クエリパラメータには
encodeURIComponent()を使用します。
最新のフレームワーク(React、Angular、Vue)はデフォルトで自動エスケープしますが、dangerouslySetInnerHTMLや同様のエスケープハッチを使用する場合は注意が必要です。
2. 入力検証とサニタイズ
許可リストを使用してサーバー側で入力を検証します。リッチテキストの場合は、DOMPurifyなどのライブラリを使用してHTMLをサニタイズし、危険なタグや属性を削除します。
const clean = DOMPurify.sanitize(userInput);
クライアント側の検証だけに依存しないでください。
3. コンテンツセキュリティポリシー(CSP)
CSPは強力な多層防御メカニズムです。実行可能なスクリプト、インラインスクリプト、その他のリソースのソースを制限します。厳格なCSPは、注入が発生した場合でもXSSをブロックできます。
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; object-src 'none';
unsafe-inlineとunsafe-evalは避けてください。正当なインラインスクリプトにはnonceまたはハッシュを使用します。
4. 安全なCookie属性
CookieにHttpOnly(JavaScriptアクセスを防止)とSecure(HTTPSのみ)を設定します。これにより、XSSによるセッションハイジャックを軽減できます。
5. 最新のフレームワークを使用し、危険なAPIを避ける
React、Angular、Vueなどのフレームワークは自動的にデータをエスケープします。innerHTMLによる直接的なDOM操作は避けてください。HTMLを挿入する必要がある場合は、まずサニタイズします。
6. 定期的なセキュリティテスト
SAST、DAST、手動ペネトレーションテストを組み込みます。OWASP ZAPなどのツールは、XSS脆弱性の特定に役立ちます。
XSSタイプと主な防御策の比較
| XSSタイプ | 説明 | 主な防御策 |
|---|---|---|
| 反射型 | リクエスト内のペイロードが応答に反映される | 出力エンコーディング、入力検証 |
| 格納型 | ペイロードがサーバーに保存され、全ユーザーに配信される | サニタイズ、出力エンコーディング、CSP |
| DOMベース | 安全でないDOM APIを介したクライアント側注入 | 安全なDOM API、CSP、evalの回避 |
ステップバイステップ:XSS防御の実装
- すべての入力ポイントを特定する: フォーム、URLパラメータ、ヘッダー、Cookie。
- コンテキストに応じた出力エンコーディングを適用する: 組み込み関数またはOWASP Java Encoderなどのライブラリを使用します。
- リッチテキストをサニタイズする: DOMPurifyなどを使用します。
- CSPを導入する: レポートのみのモードから始め、その後強制します。
- HttpOnlyおよびSecure Cookieを設定する。
- 開発者を教育する: セキュアコーディング手法についてトレーニングします。
- 定期的にテストする: セキュリティテストをCI/CDに統合します。
FAQ
XSSとCSRFの違いは何ですか?
XSSは被害者のブラウザで悪意のあるスクリプトを実行しますが、CSRFはユーザーが認証されているサイトに不正なリクエストを送信するようにブラウザを騙します。XSSはCSRF保護をバイパスするために使用されることがあります。
コンテンツセキュリティポリシーはXSSを完全に防ぐことができますか?
いいえ、CSPは多層防御の手段です。リスクを大幅に軽減しますが、適切な出力エンコーディングと入力検証に代わるものではありません。設定が不適切なCSPでは、一部の攻撃が依然として可能です。
クライアント側の検証だけでXSSを防ぐのに十分ですか?
いいえ。クライアント側の検証は簡単にバイパスできます。常にサーバー側で検証とエンコーディングを行い、すべてのクライアントデータを信頼できないものとして扱ってください。
結論
XSSは持続的な脅威ですが、出力エンコーディング、入力検証、CSP、安全なCookieといった多層防御戦略により、効果的に軽減できます。警戒を怠らず、フレームワークを最新に保ち、継続的にテストしてください。
追加のセキュリティツールについては、サーバーログの不審なパターンを検出するNginx Log Analyzerをご覧ください。