はじめに
Webアプリケーションのセキュリティを語る上で、避けて通れない脆弱性のひとつが「XSS(クロスサイトスクリプティング)」です。
近年、SNSやECサイトなど、あらゆるサービスでXSSが狙われた攻撃例が報告されています。
本記事では、XSSとは何か、その仕組み、被害例、そして基本的な対策方法までをわかりやすく解説します。
XSS(クロスサイトスクリプティング)とは?
XSSとは、Webサイトに悪意のあるスクリプトを注入し、利用者のブラウザ上でそのスクリプトを実行させる攻撃のことです。
これにより攻撃者は任意のHTMLやスクリプトを標的のサイトに埋め込むことが可能です。
例えば、以下のようにユーザの入力を出力するコードがあるとします。
<html>
{ユーザの入力を出力するコード}
</html>
PHPの場合以下になります。
※ 変数「data」にはユーザの入力値が格納されていることとします。
<html>
<?php echo $data; ?>
</html>
ユーザが「Hello.」を入力した場合、以下のHTMLが生成されます。
<html>
Hello.
</html>
このユーザの入力にJavaScriptやHTMLそのものが入っている場合、それらを実行してしまいます。
試しに「<b>Test</b>」を入力した場合を考えます。
※ HTMLの「<b></b>」タグはタグで囲った文字を太文字で表示します。
すると出力は以下となり、画面上では、<b>Test</b>
ではなく、太文字の**Test
**が出力されます。
<html>
<b>Test</b>
</html>
XSSの種類
XSSは大きく分けて次の3種類があります。
1. 反射型XSS(Reflected XSS)
ユーザーのリクエストに含まれた悪意あるスクリプトが、サーバからそのままレスポンスに反映されてしまうタイプです。
例えば、検索フォームに <script>alert('XSS');</script>
のようなコードを入力すると、そのまま検索結果ページに表示され、スクリプトが実行されます。
- 特徴:一時的に実行される
- 攻撃例:フィッシングサイトへの誘導、セッションハイジャック
2. 永続型XSS(Stored XSS)
攻撃者が送信した悪意あるスクリプトが、サーバ上のデータベースなどに保存され、それを閲覧した他のユーザーに対してスクリプトが実行されるタイプです。
例えば、掲示板に悪意のある投稿をし、それを見たユーザーのブラウザでスクリプトが動くといったケースです。
- 特徴:サーバ側に永続的に残るため、被害が拡大しやすい
- 攻撃例:Cookie窃取、管理画面への不正アクセス
3. DOMベースXSS(DOM-Based XSS)
サーバを介さず、ブラウザ側のJavaScriptによるDOM操作を通じてスクリプトが実行されるタイプです。
例えば、URLのパラメータから取得した値を、そのままHTMLに挿入する処理があると、悪意あるスクリプトが実行されることがあります。
- 特徴:フロントエンドコードの実装ミスが原因
- 攻撃例:動的ページ生成時のスクリプト実行
XSSによる被害例
- ユーザーのログイン情報(セッションID)の窃取
- 不正なリダイレクトによるフィッシング詐欺
- ユーザーアカウントの乗っ取り
- ブラウザ経由で端末へのマルウェア感染
- サイトの改ざん・なりすまし投稿
一度でもXSSが成功すると、ユーザーに深刻な被害が発生するだけでなく、サービス提供者側も社会的信用を失うリスクがあります。
簡単なデモ
デモアプリのソースコードはこちらから取得してください。「https://www.dropbox.com/scl/fi/uvzd7o629nst81q40nbe1/springfield.zip」
アプリが立ち上がったら、XSSの一覧から「Basic」にアクセスを行ってください。
URLを確認すると、パスが「/xss/basic?data=Hello.」になっており、画面に「Hello.」と出力されます。

この時のHTMLソースコードを確認した画面が以下になります。
※ ソースコードはブラウザ上で「右クリック → ページのソースを表示」または、キーボードショートカット「command + U」(Macの場合)で表示することができます

「data」パラメータの値がユーザの入力値になっており、HTML上に出力されていることがわかります。
上記のURLパラメータ「data」の値を「<strike>Hello.<strike>」に変えてアクセスしてみてください。
(URLパスを「/xss/basic?data=<strike>Hello.</strike>」にする)
※ 「<strike></strike>」タグは、文字を打ち消すのに使用します。
すると、以下のように「Hello.」が打ち消された状態で出力されます。
本当であれば「<strike>Hello.</strike>」と出力してほしいですよね。

出力されたHTMLソースコードを見ると以下のようになります。

このようにユーザが入力したHTMLタグを単なる文字列としてではなく、そのままHTMLタグとして認識してしまっています。
これを避けるには出力値のエスケープ(記号などの特殊文字を無害な文字列に変換する作業)をする必要があります。
対策
XSSの対策をしていないPHPコードは以下でした。
<html>
<?php echo $data; ?>
</html>
殆どのWebプログラミング言語には特殊文字をエスケープする関数や機能が用意されています。
PHPの場合、「htmlentities」や「htmlspecialchars」という関数が用意されており、特殊文字のエスケープを行ってくれます。
XSSの対策がされているPHPコード
<html>
<?php htmlentities($data, ENT_QUOTES); ?>
</html>
XSS対策が行われている画面を触ってみましょう。
XSSの一覧から「Basic(対策済)」にアクセスしてください。
「data」パラメータの値は先ほどと同じ「<strike>Hello.</strike>」になっていますが、XSSの対策がされているので、「Hello.」が打ち消しされておらず、「<strike>」と「</strike>」も文字列として出力されています。

ソースコードを見ると、ユーザの入力がエスケープされ、HTMLタグとして使用される「<」が「<」に、「>」が「>」になっているのがわかります。

JavaScriptの実行
JavaScriptの実行確認によく使用されるのが「alert()」関数です。
JavaScriptの「alert()」関数はブラウザ上にメッセージボックスのポップアップを表示する関数になり、視覚的にもわかりやすいのでよく使用されています。
実際にXSSの脆弱性があるBasicに戻り試してみましょう。
URLパラメータ「data」に「<script>alert(1)</script>」を挿入します。
※ 「<script></script>」タグはHTML文書内でJavaScriptコードを実行するためのタグであり、「<script>alert(1)</script>」はJavaScriptの「alert(1)」を実行することを意味します。
※ この時のURLパスは「/xss/basic?data=<script>alert(1)</script>」になります。
※ 「alert」関数の引数に1を指定しているのは通例みたいなものなので、他の値でも構いません。
すると以下のようにブラウザ上にアラートが表示され、JavaScriptが実行されたことが確認できます。

ポップアップのOKを押下後、ソースコードを表示してみます。
※ HTMLでは、「<script></script>」タグの中をJavaScriptとして実行します。

実際の攻撃例
上記のように「<b></b>」タグで文字を太くしたり、アラートを表示するだけでは攻撃者に何のメリットもありません。
実際にXSSの脆弱性を利用して攻撃者が行うことの代表例は以下になります。
- JavaScriptを使用してブラウザに保存されているセッション情報(Cookie)にアクセスを行い盗み、他人のアカウントで不正にログインを行う。
- クレジットカードの入力画面等、重要な入力を行う画面にて入力値を攻撃者のサーバに送信するスクリプトを埋め込み、情報を窃取する。
- 偽の入力画面を埋め込み、入力された値を窃取する。
偽の画面を埋め込んでみる
試しに以下のHTMLを入力を「data」パラメータに挿入してアクセスを行ってください。
<h1>クレジットカード情報入力画面</h1><form><div><label for="number" style="padding-right: 75px;">カード番号:</label><input type="text" name="number"></div><div><label for="expire" style="padding-right: 90px;">有効期限:</label><input type="text" name="expire" style="width: 70px;"></div><div><label for="security" style="padding-right: 10px;">セキュリティコード:</label><input type="text" name="security" style="width: 70px;"></div><div><label for="name" style="padding-right: 122px;">氏名:</label><input type="text" name="name"></div></form>
すると以下のように偽のクレジットカード入力画面が表示されます。
