JSONPという字面を久々に見ることがあり、なついなーと思ってCORSとの違いについてまとめてみた。
JSONPとCORSの共通点
どちらもブラウザの同一生成元ポリシーによる制限を潜り抜け、クロスオリジンでの通信・アクセスができるようになる仕組みである。
今ではほとんどの場合、CORSが使われる。
同一生成元ポリシー
同一生成元ポリシーは、ホスト、スキーム、ポートをひと組みとしてオリジンとすべて一致するときに同一生成元とみなす考え方のことで、オリジンと異なるコンテンツに対してセキュリティ上の干渉を阻止するための制限である。
1995年にNetscape社によってJavaScriptと同時にブラウザに導入された。
具体的には下記のような制限がある。
- XMLHttpRequest(Ajax)による取得の禁止
- スクリプトによる別のオリジンであるiframeやwindowに対する操作の制限(iframe.contentWindow、window.parent、window.open、window.openerなど)
- Canvasへの一部の操作の制限
この制限がない場合、例えばログインしないとみることのできない情報のあるサイトにログインしているとき、ほかの生成元のサイトからそれを取得されるなどの危険が生じる。
JSONP(JSON with padding)
JSONPは、scriptタグを利用してクロスドメインのデータを取得する仕組み。
同一生成元ポリシーは画像やJavaScript、CSSの埋め込みは制限されていない。
これを利用して、HTMLのscriptタグのsrc属性に別ドメインのURLを指定し、データを取得する手段がJSONPである。
例えば、phpプログラムのパスをsrc属性に設定し、任意のクエリ文字列を指定する。
phpプログラムでそのクエリ文字列を受け取り、それに応じたjsonオブジェクトを出力することで、 オリジンのJavaScriptで扱えるといった実装がよくなされる。
<!-- こんなかんじ -->
<script src="https://example.com/getjson?callback=XXX"></script>
扱う上での注意点として、CSRFの脆弱性に注意が必要である。
src属性に設定するエンドポイントは外部に公開されているため、悪意のあるサイトがデータを取得するといったことが可能であることから、機密情報や個人情報などのデータを取り扱うには不向きである。
またオリジンにおいてもリモートサイトから任意の内容のデータをページに差し込むことが可能であるため、JavaScriptインジェクションに対する脆弱性がある場合は、注意が必要である。
そのためデータを提供するサーバ側では、リクエストの正当性を検証する必要がある。
いまではクロスオリジンでXHR(XMLHttpRequest – JavaScriptなどのウェブブラウザ搭載のスクリプト言語でサーバとのHTTP通信を行うためのAPI)を行うことができる環境が普及したことでJSONPはバッドパターン(相手側のオリジンに悪意があればなんでもできてしまう)として使用されることがなくなった。
CORS(Cross-Origin Resource Sharing – オリジン間リソース共有)
追加のHTTPヘッダーを使用して、異なるオリジンにもリソースへのアクセス権を与えるようブラウザーに指示するための仕組みのこと。
具体的には別オリジンのリソースに下記のようなレスポンスヘッダーを追加する必要がある。
Access-Control-Allow-Origin: https://example.com // 特定のサイトを許可
Access-Control-Allow-Origin: * // すべてのサイトを許可
Access-Control-Allow-Headers "X-Requested-With, Origin, X-Csrftoken, Content-Type, Accept" // 許可するヘッダーの定義
GET、POST、HEADリクエストを利用する場合は、「Access-Control-Allow-Headers」を指定する必要はないが、それ以外のメソッドを利用する場合は下記のようにアクセスを許可するメソッドをレスポンスヘッダーに含める必要がある。
Access-Control-Allow-Methods: PUT, DELETE, PATCH
オリジン側の注意点としては、JavaScriptのFetch APIを使用する場合「cors mode」の設定を行う必要がある。
fetch('http://example.com', {
mode: "cors",
});
またCookieも含めてリクエストを送りたいという場合、デフォルトでは異なるオリジンに対してCookieは送信されず、Fetch APIやXMLHttpRequestなどにオプションを付与する必要がある(別オリジンも「Access-Control-Allow-Origin: *」としている場合は動作しないため、特定のサイトを記述する必要がある)。
CORSはSOP(Same-Origin Policy – 同一オリジンポリシー)という同じオリジン内でしかデータの送受信をさせない機能の例外ルールとして定められたもので、この制限は自由度の高いJavaScriptに制限をかけるために導入されたもの(画像はクロスオリジンでも簡単にHTML上に表示できるが、canvasなどのAPIを利用してクロスオリジンの画像を呼び出すことはできない)で、相手側オリジンの許可をえることでアクセスを可能にするプロトコルである。
SOPの考え方は、同一オリジンならスクリプト自身のサービスなので通信し、異なるオリジンなら他者のため通信させない、といった動きになる。
攻撃者が情報を盗むポイントとしてはブラウザ、ネットワーク、サーバーの 3 つに大きく分かれ、ブラウザでの攻撃は、いかにしてドメインの壁を越えるか、であるが、ブラウザ上で異なるドメイン同士の連携を可能にしながらもサイトごとの安全性をある程度保ってくれるのがSOPである。
SOPが有効に働いている例を紹介する。
例えば埋め込み動画を例に考えてみましょう。この cross-origin な動画は iframe で埋め込まれているため、サードパーティー Cookie を使ってパーソナライズが可能になっています。閲覧しているユーザーは配信元のドメインでログイン状態であれば、そのアカウントに対して「後で見る」などのアクションが可能なため、便利に利用することができます。しかし、特に API が用意されていない限り、埋め込み元のサイトからこのアカウントの情報にアクセスすることはできません。iframe から得られる
https://blog.agektmr.com/2021/11/browser-security.htmlwindow
オブジェクトの DOM ツリーを辿っても、得られる情報は限られているからです。どんな HTML が表示されているか、ましてや Cookie の中身などを見ることは不可能になっています。
Popup ウィンドウとして開かれたウィンドウとの間でも同様のことが言えます。例えば典型的な支払いサービスでwindow.open()
を使って開いたウィンドウと連携されている場合に DOM ツリーを辿れてしまうと、お店側がユーザーのクレジットカード情報などを盗み見ることができてしまいます。そのため、window.open()
の戻り値からも、開かれたウィンドウのwindow.opener
からも、辿れる情報はブラウザが制限しています。
このように、cross-origin なスクリプトから任意の情報にアクセスできないように制御しているのが、ブラウザの Same-Origin Policy です。