本記事は『フロントエンド開発のためのセキュリティ入門 知らなかったでは済まされない脆弱性対策の必須知識』(著者:平野昌士、監修:はせがわようすけ)の「第5章 XSS」から一部を抜粋したものです。掲載にあたって一部を編集しています。
能動的攻撃と受動的攻撃
Webアプリケーションへの攻撃には2つのパターンがあります。それが「能動的攻撃」と「受動的攻撃」です。まずはこの2つの違いについて理解しておきましょう。
能動的攻撃とは
能動的攻撃は、攻撃者がWebアプリケーションへ直接攻撃コードを送るタイプの攻撃です。データベースを不正に操作するためのSQLをサーバへ送信する「SQLインジェクション」や、OSを不正に操作するためのコマンドをサーバへ送信する「OSコマンドインジェクション」といった攻撃があります(図5-1)。
受動的攻撃とは
受動的攻撃は、攻撃者が用意した罠を利用して、Webアプリケーションを訪れたユーザー自身に攻撃コードを実行させる攻撃手法です。能動的攻撃とは異なり、攻撃者は直接Webアプリケーションへ攻撃をしません。攻撃のトリガーになるのは、ページへのアクセスやリンクのクリックなどユーザーによる操作です。たとえば、攻撃者が用意した罠サイトへユーザーがアクセスしたとき、ページに仕掛けられた罠を経由して対象のWebアプリケーション内で攻撃コードが実行されます(図5-2)。
受動的攻撃の被害には、機密情報の漏えいやユーザー権限を悪用したWebアプリケーションへの攻撃などがあります。さらに、罠にひっかかったユーザー自身が攻撃コードを実行することになるため、攻撃者が直接アクセスできないイントラネットのWebアプリケーションや、ログイン後のページに対しても攻撃ができます。
サーバを介さないブラウザで完結する攻撃手法の場合、サーバにログを残すこともできないため、Webアプリケーションの運営者は攻撃を検知することができません。
次の4つはWebアプリケーションにおける代表的な受動的攻撃です。
- XSS(クロスサイトスクリプティング)
- CSRF(クロスサイトリクエストフォージェリ)
- クリックジャッキング
- オープンリダイレクト
能動的攻撃はサーバが直接攻撃されるため、サーバサイドで対策しなければいけません。しかし、受動的攻撃の中にはフロントエンドだけで成立するものもあります。そのため本書では、特にフロントエンドに関係するこれらの受動的攻撃を取り上げます。本記事ではXSSについて説明します。
XSSとは
XSS(クロスサイトスクリプティング)とは、Webアプリケーション内の脆弱性を利用して不正なスクリプトを実行する攻撃です。クロスオリジンのページで実行されるJavaScriptからの攻撃は同一オリジンポリシーによってブロックされますが、XSSは攻撃対象のページ内でJavaScriptを実行するため、同一オリジンポリシーでは防ぐことができません。被害の大きさは様々ですが、脆弱性対策情報データベース「JVN iPedia」や「HackerOne」などの脆弱性報奨金サイトへの報告件数が最も多いのはXSSです。
脆弱性診断ツールを利用してもすべての攻撃手法を考慮して対策をすることは困難です。特に、ブラウザ上で動くJavaScriptが原因で発生するXSSも多いため、フロントエンドでも基本的な対策を行う必要があります。
XSSの仕組み
XSSとは、攻撃者が不正なスクリプトを攻撃対象ページのHTMLに挿入して、ユーザーに不正スクリプトを実行させる攻撃手法です。XSSはユーザーが入力した文字列をそのままHTMLへ挿入することで発生する脆弱性です。たとえば、次のようなURLがあったとします。このURLはあるショッピングサイト内の商品検索画面へ遷移するURLと仮定してください。
https://site.example/search?keyword=セキュリティ
keywordは検索キーワード用のクエリ文字列と考えてください。keywordの値はデータベースの検索に使われるだけでなく、HTMLに挿入されるとします。たとえば、keyword=セキュリティの場合、次のようなHTMLの結果になります(図5-3の①)。
このようなリクエストに含まれる文字列を、そのままHTMLへ挿入する処理はXSSの危険性があります。たとえば、次のようなURLでリクエストしたとします。
https://site.example/search?keyword=<img src onerror="location.href='https://attacker.example
'" />
レスポンスのHTMLは次のようになります。
<div id="keyword">
検索ワード: <img src onerror="location.href='https://attacker.example
'" />
</div>
XSS脆弱性を利用されて、<img>要素が埋め込まれています。この<img>要素のsrc属性は正しく設定されていないため、エラーと扱われてonerror属性に設定されたJavaScriptが実行されます。
この例であれば、location.href='https://attacker.example'が実行されて、強制的に別のWebサイトへリダイレクトしてしまいます。このサンプルコードでは、攻撃者が用意した罠サイトへ強制的にリダイレクトさせるコードを実行させていますが、その他にも機密情報の漏えいやWebアプリケーションの改ざんなど様々な攻撃が可能です。
XSSの脅威
すべてのXSS脆弱性を考慮した対策を施すことは困難です。熟練の開発者や脆弱性診断ツールが脆弱性なしと判断しても、XSS攻撃を成功させられるケースもあります。「YouTube」や「Twitter」といった著名なWebサービスでも過去にはXSS脆弱性が見つかっています。
2018年の情報ですが、情報処理推進機構(IPA)やGoogleなどが行う脆弱性報奨金制度へ届け出されたWebアプリケーションの脆弱性についても、最も届出が多かったのはXSSでした(図5-4)。
2021年にはECサイトでのXSSによって、クレジットカード情報を漏えいさせるような重大な被害も発生しています。
XSS脆弱性を完全に排除することは難しく、すぐにゼロになることはないでしょう。ただ、一概にすべてのXSSが重大な問題を引き起こすわけではありません。Webアプリケーションの性質や発生するXSSによっては、大きな被害にならないケースもあります。
しかし、XSSには被害の大きさにかかわらず様々な脅威を与える可能性があります。XSSの脅威には、次のようなものがあります。
-
機密情報の漏えい
Webアプリケーション内の機密情報を奪取して攻撃者のサーバへ送信される -
Webアプリケーションの改ざん
偽の情報を表示するためにWebアプリケーションが改ざんされる -
意図しない操作
本来のWebアプリケーションの動作とは異なる動作やユーザーの意図しない操作が実行される -
なりすまし
攻撃者はユーザーのセッション情報を奪取して、そのユーザーになりすます -
フィッシング
偽の入力フォームが表示され、ユーザーの個人情報やアカウント情報(ユーザーID、パスワードなど)を入力してしまうことで、重要な個人情報が盗み取られる
3種類のXSS
XSS攻撃には様々な手法がありますが、CWE(Common Weakness Enumeration:共通脆弱性タイプ)ではXSSを大きく次の3つに分類しています。
- 反射型XSS(Reflected XSS)
- 蓄積型XSS(Stored XSS)
- DOM-based XSS
反射型XSSと蓄積型XSSはWebアプリケーションのサーバサイドのコードの不備が原因で発生し、DOM-based XSSはフロントエンドのコードの不備が原因で発生します。それぞれの発生までの経路は異なりますが、3種類とも最終的にユーザーのブラウザで攻撃コードが実行されるという共通点があります。
反射型XSS(Reflected XSS)
反射型XSS(Reflected XSS)とは、攻撃者が用意した罠から発生するリクエストに対して、不正なスクリプトを含むHTMLをサーバで組み立ててしまうことが原因で発生するXSSです(図5-5)。リクエストに含まれるコードをレスポンスのHTML内にそのまま出力することから「反射型XSS」と呼ばれています。
反射型XSSはリクエストの内容に不正なスクリプトが含まれたときのみ発生し、持続性がないことから「非持続型XSS」(Non-Persistent XSS)と呼ばれることもあります。不正なスクリプトを含むリクエストを送信したユーザーだけが反射型XSSの影響を受けます。
リクエストの内容をそのままレスポンスのHTMLへ反映するような処理は反射型XSSの原因となります。
蓄積型XSS(Stored XSS)
蓄積型XSS(Stored XSS)とは、攻撃者がフォームなどから投稿した不正なスクリプトを含むデータがサーバ上に保存され、その保存されたデータ内の不正なスクリプトがWebアプリケーションのページに反映されることで発生するXSSです(図5-6)。不正なスクリプトを含むデータがサーバ内へ蓄積されていくことから「蓄積型XSS」または「格納型XSS」と呼ばれています。
蓄積型XSSは、データベースに登録されたデータが反映されるページを閲覧するすべてのユーザーに影響を及ぼします。反射型XSSと異なり攻撃は一度きりではなく、正常なリクエストをしたユーザーにも被害をもたらす可能性があります。サーバに保存された不正なスクリプトを含むデータを削除したり、アプリケーションのコードを修正したりしないと、蓄積型XSSの被害を止めることはできません。このように持続性のあるXSS攻撃であることから「持続型XSS」(Persistent XSS)と呼ばれることもあります。
たとえば、あるユーザーが投稿したテキストや画像を他のユーザーも閲覧できるSNSサービスがあったとします。攻撃者は次のようなXSSを引き起こす不正なコードを入力フォームに入力して投稿しました。
<img src onerror="location.href='https://attacker.example
'" />
投稿されたデータはサーバ内へ保存され、そのまま他のユーザーが閲覧できるページへ反映されます。すると、この投稿を閲覧するすべてのユーザーがXSSの被害を受けることになり、閲覧するたびにXSS攻撃が発生します。このように不特定多数のユーザーに対して何度もXSS攻撃ができてしまうため、蓄積型XSSはXSSの中でも最も危険な攻撃です。
DOM-based XSS
DOM-based XSSとは、JavaScriptによるDOM(Document Object Model)操作が原因で発生するXSSです。他のXSSがサーバのコードの不備が原因となるのに対して、DOMbased XSSはフロントエンドのコードの不備によって発生します。また、サーバを介さないので攻撃を検知することが難しいという特徴もあります。フロントエンドのJavaScriptはデベロッパーツールでコードの中身を見ることができる、という点からも攻撃者に狙われやすい脆弱性です。DOM-based XSSは本書のテーマであるフロントエンドとの関連性が高いため、他の2つより詳しく説明します。
DOMについておさらい
DOM-based XSSの仕組みを知るために、まずはDOMについて簡単におさらいしておきましょう。DOMとは、HTMLを操作するためのインタフェースです。ブラウザはHTMLの構文を解析してDOMツリーと呼ばれる構造体を生成します。生成されたDOMツリーはJavaScriptで内容を変更することができます。DOMツリーの内容が変われば、DOMツリーの元となるHTMLも書き換わるため、JavaScriptで画面の表示を変更することができます。DOMの仕様はWHATWGの「DOM Standard」に定義されています。
DOMツリーについてイメージをつかむために、もう少し詳しく見ていきましょう。次のHTMLを例にします。
<html> <head> <meta charset="utf-8"> <title>Top Page</title> </head> <body> <p>ようこそ</p> </body> </html>
このHTMLをDOMツリーで表すと次のような図になります(図5-7)。
DOMツリーを変更するには、次のようにJavaScriptで操作します。この例では
要素の中身を書き換えています。
document.body.innerHTML =
'<a href="https://attacker.example
">新しいサイトはこちら</a>';
すると、DOMツリーは次のように変わります(図5-8)。
DOMツリーの変更に連動してHTMLは変更されます。
<html> <head> <meta charset="utf-8"> <title>Top Page'</title> </head> <body> <a href="https://attacker.example">新しいサイトはこちら'</a> </body> </html>
このようにしてJavaScriptを使ってHTMLを変更することをDOM操作と呼び、これによって表示が動的なページを作ることができます。
DOM-based XSSが発生する例
では、このDOM操作を利用してどのように攻撃を受けるのでしょうか。DOM-based XSSの攻撃手法を見ていきましょう。ここでは、URL内の#以降の文字列を画面上に表示する例をもとに説明します。たとえば、次のURLがあったとします。
https://site.example/#こんにちは
次のコードは#こんにちはから#文字を取り除いたこんにちはという文字列をDOMへ挿入しています(リスト5-1)。decodeURIComponent(location.hash.slice(1))はこんにちはを取得する処理です。
https://site.example/#こんにちは
次のコードは#こんにちはから#文字を取り除いたこんにちはという文字列をDOMへ挿入しています(リスト5-1)。decodeURIComponent(location.hash.slice(1))はこんにちはを取得する処理です。
const message = decodeURIComponent(location.hash.slice(1)); document.getElementById("message").innerHTML = message;
この結果、次のように<div>要素へこんにちはが反映されます。
<div id="message">こんにちは</div>
しかし、次のようなURLだった場合、DOM-Based XSSが発生します。
https://site.example/#<img src=x onerror="location.href='https://attacker.example
'" />
このURLでアクセスした結果のHTMLは次のようになります。
<div id="message">
<img src=x onerror="location.href='https://attacker.example
'" />
</div>
挿入された文字列は<img>要素としてブラウザに解釈され、onerror属性に指定されたlocation.href='https://attacker.example'のJavaScriptコードが実行されます。
この例では、innerHTMLを使ったDOM操作がDOM-based XSSの原因です。前述の通り、innerHTMLを使えば、HTMLの中身を取得したり書き換えたりすることができます。そのため、location.hash.slice(1)から取得した<img src onerror="location.href='https://attacker.example'" />の文字列がinnerHTMLを介してHTMLへ挿入されてしまいます(図5-9)。
DOM-based XSSはブラウザが持つ機能の使用が原因で発生します。DOM-based XSSの原因になるブラウザの機能は「ソース」と「シンク」に分類できます。DOM-based XSSを引き起こす原因となる文字列である、location.hashのような箇所を「ソース」と呼び、ソースの文字列からJavaScriptを生成、実行してしまう箇所を「シンク」と呼びます。
ソースとして動作する機能の代表例には次のようなものがあります。
- location.hash
- location.search
- location.href
- document.referrer
- postMessage
- Webストレージ
- IndexedDB
シンクとして動作する機能の代表例には次のようなものがあります。
- innerHTML
- eval
- location.href
- document.write
- jQuery()
これらの機能が一概に危険なものというわけではありませんが、利用する際には注意が必要です。これらの機能を利用する際に、与えるデータに対して適切に処理を行っていれば、XSSは発生しません。
XSSの対策は書籍にて
本書ではXSS対策の仕組みについて、エスケープ処理などの基本的な対策方法をハンズオン形式で解説しています。実際に手を動かして学びたい方はぜひ『フロントエンド開発のためのセキュリティ入門 知らなかったでは済まされない脆弱性対策の必須知識』でチェックしてみてください。