はじめに
業務にて、AWS S3のバケットに画像を格納しています。
その際に、やり取りをしている外部の有識者から、こんなことを言われました。
「画像取得の処理についてです。対象の画像は様々なオリジンからのアクセスされる可能性があります。対象サーバにてCORS設定の対応をお願いします。」
そもそも、CORSとは何だ?といったことから、S3バケットにCORS設定を行うに当たり、色々調べてみました。間違いなどありましたら、ご指摘ください。
1. CORSとは何か
オリジン間リソース共有(Cross-Origin Resource Sharing)のこと。
HTTPヘッダーベースの仕組みを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザに指示するための仕組みです。
セキュリティ上の理由から、ブラウザは、スクリプトによって開始されるオリジン間HTTPリクエストを制限しており、同一オリジンポリシーに従います。
ブラウザで動作しているアプリケーションが読み込まれたのと同じオリジンに対してのみリソースのリクエストを行うことが可能です。
※下記サイト意訳
うむ、あまりよくわかんないですね。
まずは関連する各用語について、詳しく見ていきます。
2. 各用語について
同一オリジンポリシー(Same-Origin Policy)
ブラウザ上で動作するアプリ(JavaScript)から、サイトをまたがったアクセスを禁止するセキュリティ上の制限のことを指します。
「オリジン」や「同一オリジン」についても見ていきましょう。
オリジンと同一オリジン
オリジンとは、下記3要素の組み合わせのことを指します。
①プロトコル
例:http
or https
②ホスト
例:example.com
③ポート
例::80
,:443
,:8080
そのうえで、同一オリジンである条件は、下記3点をすべて満たす場合のみです。
- URLのホストが一致している
- スキーム(プロトコル)が一致している
- ポート番号が一致している
例えば、下記AとBは③ポート番号が一致していないため、同一オリジンとは言えません。
A. https://example.com:443
B. https://example.com:8080
対して、下記はパスが異なりますが、①プロトコル②ホスト③ポートのすべてが一致しているため、オリジンは同一と捉えます。
C. https://example.com/pages1
D. https://example.com/pages2
「オリジン」と「同一オリジン」についてはこんなところでしょうか。
同一オリジンポリシーに話を戻します。
そもそもですが、なぜブラウザには同一オリジンポリシーが設定されているのでしょうか。
3. 同一オリジンがブラウザに設定されている目的
ブラウザはどのWebアプリにおいてもJavaScriptが実行できます。
そのうえで、JavaScriptはCookieやLocalStorageにもアクセス可能です。
CookieやLocalStorageなどには、重要な情報も多いため、JavaScriptのアクセス権限を制限しない場合には、他Webアプリが情報を読み取るスクリプトを埋め込むことが可能となり、機密情報の漏洩に繋がります。
これらを防ぐため、ブラウザには同一オリジンポリシーが設定されています。
では、どういった攻撃に対し、同一オリジンポリシーが有効か、具体例を見ていきましょう。
受動的攻撃への対策
同一オリジンポリシーは、受動的攻撃への対策として有効です。
受動的攻撃とは、罠を利用してユーザーに攻撃コードを実行させる攻撃のことを指します。
例として挙げられるものは、XSSやCSRFなどがあります。
では、同一オリジンポリシーがなぜ有効になるか、具体例を挙げてみます。
ユーザーの機密情報を保護する
ブラウザ上のCookieなどに保存されているユーザー個人の情報や、ログイン情報等を攻撃者に盗まれないようすることが可能です。
XSS攻撃の防止
同一オリジンポリシーは、一部のXSS攻撃にも有効となります。
※XSS:Webサイトの脆弱性を利用して、攻撃者が悪意あるスクリプトを埋め込み、ユーザーのブラウザ上で実行させる攻撃
利用者がブラウザ上でM銀行と、XSS脆弱性を持つZサイトを別タブとして開いているケースを考えてみます。
- 攻撃者はXSS脆弱性があるZサイトに悪意あるスクリプトを埋め込む
- Aさんは、ブラウザの別タブでM銀行とXSS脆弱性のあるZサイトを閲覧している
- 攻撃者は、Zサイトを閲覧しているAさんが別タブとして開いているM銀行の情報へアクセスを試行し、M銀行のデータを抜き取ろうとする
上記のケースで、同一オリジンポリシーが有効になります。
上記の場合では、同一オリジンポリシーにより、攻撃者はM銀行のAさんの利用者情報にアクセスすることができず、データを盗むことができません。
同一オリジンポリシーが有効とならないケース
XSS攻撃の防止の冒頭にて、同一オリジンポリシーは一部のXSSに有効と記載しました。
上述のケースの場合では、M銀行に脆弱性が存在する場合において、M銀行の利用者情報はXSSにより攻撃者に抜き取られることがあり得ます。これは、同一オリジン内で、XSSが実行されるためです。
4. CORSの必要性
上述のような理由により、ブラウザには同一オリジンポリシーが設定されていることがわかりました。
ただ、信頼関係ができているサイト同士でデータのやり取りを実施したい際には、同一オリジンポリシーの制限があるため実行できません。
そこで「データのアクセスを許可できるWebサイトに対して、同一オリジンを超えたアクセスを可能とする仕組み」として策定されたのが、CORSです。
5. CORS設定時の挙動
CORS設定に関するリクエストには、単純リクエストとプリフライトリクエストが存在します。
それぞれがどんなリクエストなのか、各々の挙動についてみていきます。
単純リクエスト
単純リクエストは、下記の条件をすべて満たすHTTPリクエストです。
- HTTPメソッドが下記のいずれか
・GET
・HEAD
・POST
- HTTPヘッダが下記の範囲内であること
・Accept
・Accept-Language
・Content-Language
・Range
・Content-Type
(ただし、値はapplication/x-www-form-urlencoded
multipart/form-data
text/plain
のみであること) - リクエストに ReadableStream オブジェクトが使用されていないこと。
- XMLHttpRequestの場合、オブジェクト.upload.addEventListener()が使用されていない
上記をすべて満たす場合のリクエストは、単純リクエストと呼ばれます。
単純リクエストにおけるCORS設定時のフロー
単純リクエストにおいては、ブラウザは下記のような挙動を行います。
-
クライアント(利用者)は、
https://aaa.com
へアクセス -
サーバ
aaa.com
はJavaScriptを含めたレスポンスを返す -
ブラウザは追加コンテンツを取得するため、
https://bbb.com
へアクセス
→ JSの指示により、ブラウザが通信を行う。
→ ブラウザは。aaa.com
から呼び出されていることを伝えるために、Originヘッダを自動で付与GET /data HTTP/1.1 Host: bbb.com Origin: https://aaa.com
-
bbb.com
は、Access-Control-Allow-Origin
を付与してレスポンスを返す
→ ブラウザはaaa.com
へアクセスが可能と判断
→ 結果として、JSがレスポンスデータにアクセスが可能となる。HTTP/1.1 200 OK Content-Type: application/json Access-Control-Allow-Origin: https://aaa.com
プリフライトリクエスト
プリフライトリクエストとは、ブラウザが自身と異なるサーバにリクエストをする際に、事前にブラウザから送信されるHTTPリクエストのことを指します。
条件としては、単純リクエストではない場合はほぼプリフライトリクエストと捉えてよさそうです。
プリフライトリクエストは、サーバが対象のリクエストを受け付ける許可を出しているかを確認するために送信されます。
プリフライトリクエストにおけるCORS設定時のフロー
プリフライトリクエストにおいては、ブラウザは下記のような挙動を行います。
- クライアント(利用者)は、
https://aaa.com
へアクセス - サーバ
aaa.com
はJavaScriptを含めたレスポンスを返す - ブラウザは
https://bbb.com
へプリフライトリクエスト(OPTIONS)を送信
→JSがPUT
やapplication/json
を指定しているため、ブラウザが自動で事前確認(プリフライトリクエスト)を実施
→このリクエストはOPTIONS
メソッドで送信され、下記のようなヘッダを含むOPTIONS /api/data HTTP/1.1 Origin: https://aaa.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: Content-Type
- サーバ
bbb.com
は条件を受け入れるかを判断し、許可する場合はCORSヘッダを返す
→応答がない場合は、ブラウザは本リクエストを送信せず中断するHTTP/1.1 204 No Content Access-Control-Allow-Origin: https://aaa.com Access-Control-Allow-Methods: PUT, GET, POST Access-Control-Allow-Headers: Content-Type
- ブラウザは確認が通ったため、本リクエストを
https://bbb.com
に送信PUT /api/data HTTP/1.1 Origin: https://aaa.com Content-Type: application/json
-
bbb.com
は、Access-Control-Allow-Origin
を付与してレスポンスを返す
→ ブラウザはaaa.com
へアクセスが可能と判断
→ 結果として、JSがレスポンスデータにアクセスが可能となる。HTTP/1.1 200 OK Content-Type: application/json Access-Control-Allow-Origin: https://aaa.com
6. AWS S3バケットへのCORS設定
話を大きく戻します。
今回はAWSのS3バケットへCORSを設定する話でした。
AWSのS3バケットにもCORSは設定できます。
S3への設定を行うことで、S3バケットへのアクセス元ブラウザに対し、CORS設定を適用するように制限を掛け、該当のファイルに対し、アクセスを可能となります。
AWS管理画面(コンソール)から設定
- S3バケットにアクセス
- AWSコンソールにログイン → S3 → 対象のバケットを選択
- 「プロパティ」タブ → 「バケットポリシー」ではなく「CORSの設定」を探す
- メニュー内の「アクセス許可」タブを開く
- 下の方にある「CORSの設定」をクリック
最低限必要なCORS設定は以下の通りです。
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET",
"PUT",
"POST",
"DELETE"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": [
"Access-Control-Allow-Origin"
]
}
]
各記載について、確認していきます。
AllowedHeaders
AllowedHeaders
は、クライアントからサーバーへ送信するHTTPリクエストヘッダーのうち、許可するものを指定します。
*
を記載する場合は、どんなヘッダーでも許可します。
AllowedMethods
AllowedMethods
は、ブラウザが、どのHTTPメソッドを用いてS3バケットにアクセスできるかを定義します。よく記載するのは下記でしょうか。
メソッド | 用途 | S3での利用例 |
---|---|---|
GET |
リソースの取得 | ファイルのダウンロード・表示 |
PUT |
リソースの新規作成または上書き | ファイルのアップロード |
POST |
リソースの作成やフォーム送信 | マルチパートアップロード |
DELETE |
リソースの削除 | ファイルの削除 |
AllowedOrigins
AllowedOrigins
は、クロスオリジンからの読出しを許可するための仕掛けで、この欄に ""(ダブルクォーテーション)で囲み、許可するオリジンを記載します。
*
を記載する場合は、すべてのオリジンからのアクセスを許可します。
ExposeHeaders
ブラウザにアクセス可能なレスポンスヘッダーは、標準的なヘッダーからしか直接アクセスできません。
標準的なヘッダー以外から値を取得したい際に、 ExposeHeaders
に指定します。
これらを記載することで、S3バケットにCORSを設定することができます。
おわりに
今回はCORSについてと、S3バケットにCORS設定を行うために必要なことをまとめました。
CORSについては初めて知ることも多く、個人的な知見となりました。
また、SOPの理解度や各用語についても理解を深めることができたと実感しています。
参考