はじめに
Webセキュリティにおいて、XSS(クロスサイトスクリプティング:Cross-Site Scripting) は、最も古くから存在し、かつ現在でも被害件数の絶えない深刻な脆弱性の一つです。
悪意のあるスクリプトがユーザーのブラウザ上で実行されてしまうと、セッションID(Cookie)の窃取、アカウントの乗っ取り、偽の入力フォームの表示による個人情報の詐取といった深刻な被害に直結します。
本記事では、Webアプリケーション開発においてXSS脆弱性を完全に排除するための防御原則について、エスケープ、サニタイズ、安全なAPIの選択、そして防衛網の二重化まで体系的に解説します。
1. XSS脆弱性の3つの分類
XSSはその仕組みと発生経路によって、主に以下の3種類に分類されます。
- 反射型XSS(Reflected XSS): 悪意のあるスクリプトを含むURLやフォームデータをユーザーにクリックさせ、サーバーからの応答ページ内にそのスクリプトがそのまま「反射」して出力されることで実行される。
- 格納型XSS(Stored / Persistent XSS): 掲示板の投稿やユーザープロフィール欄など、サーバーのデータベースにスクリプトが「格納」され、そのページを閲覧した不特定多数のユーザーに対してスクリプトが配信・実行される。最も被害が広がりやすい。
- DOM-based XSS:
サーバー側の処理を経由せず、クライアントサイド(JavaScript)のDOM操作処理において、URLのハッシュ(
location.hash)などの入力値が安全でない方法で画面に出力されることで発生する。
2. 防御原則1: コンテキストに応じたHTMLエスケープ
最も基本的な対策は、ユーザーが入力した「動的なテキストデータ」をHTMLとして出力する際に、特別な意味を持つ文字を無害な文字列(実体参照)に変換する エスケープ処理 です。
代表的なエスケープ文字
&->&<-><>->>"->"'->'(または')
注意点: 出力する「場所」によって処理を変える
HTMLの本文(タグの外)にテキストを出力する場合と、<input value="..."> のような属性値の中にテキストを出力する場合では、必要なエスケープが異なります。特に属性値の中に値を出力する場合は、クォーテーションの適切な処理が必須です。
また、<script> タグの内部(JavaScriptコード内)や CSSスタイルシート内、URL(href 属性など)に出力する場合は、HTMLエスケープだけでは防御できません。それぞれに適したエスケープ方式(JavaScriptエスケープやURLエンコード)を適用する必要があります。
3. 防御原則2: 安全なDOM操作APIの選択(DOM-based XSS対策)
JavaScriptでページ上のテキストを書き換える際、安全でないAPIを使用すると、簡単にDOM-based XSSが混入します。
避けるべき実装
// location.searchからクエリパラメータを取得して画面に出力する
const params = new URLSearchParams(window.location.search);
const username = params.get("name");
// 危険!usernameに「<img src=x onerror=alert(1)>」が入るとスクリプトが実行される
document.getElementById("greeting").innerHTML = `こんにちは、${username}さん!`;
改善された安全な実装
HTML要素として解釈させる必要がない(純粋な文字列を表示させたい)場合は、innerHTML ではなく textContent または innerText を使用します。
const params = new URLSearchParams(window.location.search);
const username = params.get("name");
// 安全!入力文字列がそのままプレーンテキストとして描画される
document.getElementById("greeting").textContent = `こんにちは、${username}さん!`;
どうしてもリッチテキスト(HTMLタグ)を許可して描画したい場合は、そのまま出力するのではなく、DOMPurify などの信頼できるサニタイズ(有害要素排除)ライブラリを必ず噛ませる必要があります。
4. 防御原則3: リンク(a href)のプロトコル制限
もう一つ盲点になりやすいのが、ユーザーに自由なURLを入力・登録させ、それをリンク(<a href="ユーザー入力">)として出力する場合です。
危険なパターン
HTMLエスケープが正しく施されていても、ユーザーが javascript:alert(document.cookie) という文字列を入力した場合、リンクをクリックした瞬間にJavaScriptコードが実行されてしまいます。
<!-- エスケープされていても機能してしまう危険なリンク -->
<a href="javascript:alert(document.cookie)">プロフィールを見る</a>
対策
入力されたURLが http:// または https:// で始まっていることを厳格にチェック(ホワイトリスト認証)し、それ以外のスキーム(javascript: や data: など)のリンク生成を拒否します。
5. 多層防御としての安全策
プログラムの実装ミスをカバーするために、ブラウザのセキュリティ機能を併用する「多層防御」が極めて有効です。
Content Security Policy (CSP) の設定
CSPは、ブラウザに対して「このページで実行してよいスクリプトの出所(ドメイン)」や「インラインスクリプトの実行可否」をHTTPヘッダー経由で指示する仕組みです。
Content-Security-Policy: default-src 'self'; script-src 'self' https://trustedscripts.com;
CSPを適用しておくことで、万が一プログラムのバグによりHTML内に悪意のあるインラインスクリプト(<script>alert(1)</script>)が挿入されてしまっても、ブラウザ側がその実行を自動でブロックしてくれます。
Cookieの保護(HttpOnly属性)
セッション管理用のCookieには必ず HttpOnly 属性 を付与して発行します。これにより、万が一XSSが突破されて悪意のあるスクリプトが実行されてしまっても、JavaScript(document.cookie)からセッション情報にアクセスされるのを防ぎ、アカウント乗っ取りの被害を最小限に防ぐことができます。
まとめ
XSS脆弱性を防ぐためのアプローチはシンプルですが、徹底した運用が必要です。
- テキスト出力時は常にHTMLエスケープを行う(フレームワークの標準機能を過信せずコンテキストを意識する)
- JavaScriptでの描画は
innerHTMLを避け、textContentを使用する - 動的なURLは
http:///https://スキームのみに制限する - CSPの導入とCookieの
HttpOnly設定で多層防御を構築する
これらのセキュリティ設計を開発初期から組み込み、安全なウェブサイト制作を推進していきましょう。
