CORSと同一オリジンポリシーの理解:Zoomの脆弱性から学ぶ教訓
核心となる教訓:CORSはサーバーのためのセキュリティツールではない
Cross-Origin Resource Sharing (CORS) は、リクエストがサーバーに到達するのを防ぐメカニズムではなく、むしろ、フロントエンドアプリケーションがレスポンスを読み取れるように、サーバーが Same-Origin Policy (SOP) を緩和するためのブラウザ側のメカニズムです。この違いを誤解すると、開発者はCORSを回避するための「ハック」を実装したり、CORSヘッダーがバックエンドを不正なリクエストから保護していると誤解したりしがちです。
ケーススタディ:Zoomのlocalhost脆弱性
2019年、Zoomにおいて、Zoomのウェブサイトがネイティブアプリを起動できるように、アプリケーションが localhost:19421 でウェブサーバーを実行しているという脆弱性が発見されました。このローカルサーバーへのAJAXリクエストを行う際にCORSの制限を回避するため、Zoomは「画像ハック」を実装していました。これは、データを画像ファイルの寸法にエンコードするというものでした。
なぜ画像ハックがセキュリティホールを生み出したのか
ブラウザは一般的に、AJAXリクエストとは異なる制限で、オリジンをまたぐ画像の読み込みを許可しているため、この回避策はSame-Origin Policyによる保護を事実上無効化してしまいました。その結果、zoom.us だけでなく、インターネット上のあらゆるウェブサイトがローカルのZoomサーバーにリクエストを送信し、画像寸法を通じてレスポンスを読み取ることができ、ネイティブクライアントでの不正な操作を許してしまう可能性がありました。
正しい実装方法
この機能を安全にするためには、Zoomはlocalhostサーバー上にREST APIを実装し、Access-Control-Allow-Origin ヘッダーを https://zoom.us に具体的に設定すべきでした。これにより、公式のZoomウェブサイトはレスポンスを読み取ることができ、他の悪意のあるサイトからのアクセスは防ぐことができました。さらに、iframeのレンダリングをブロックするContent Security Policy (CSP) を使用すれば、Zoomサイトが悪意のあるフレーム内に埋め込まれてバックグラウンドでのアクションをトリガーされることを防ぐことができました。
SOP vs. CORS:混乱を解消する
開発者の混乱の多くは、Same-Origin Policy (SOP) と Cross-Origin Resource Sharing (CORS) の区別ができていないことに起因します。
Same-Origin Policy (SOP)
SOPは主要なセキュリティ境界です。それは、あるオリジン(プロトコル、ホスト、ポート)のドキュメントが、別のオリジンのデータにアクセスすることを防ぎます。例えば、SOPは、あなたがログインしている間に malicious-site.com が gmail.com からあなたのプライベートなメールを取得することを防ぎます。
Cross-Origin Resource Sharing (CORS)
CORSは、SOPを「緩和」するためのメカニズムです。サーバーがブラウザに対して次のように明示的に伝えることを可能にします:「私はこの特定のオリジンを信頼しています。したがって、そこで実行されているJavaScriptがこのリクエストのレスポンスを読み取ることが許可されます」。
技術的な主な違い
- 書き込み vs. 読み取り: SOPは一般的に、クロスオリジンのレスポンスの「読み取り」をブロックするものであり、リクエストの「送信」をブロックするものではありません。リクエストは送信され、サーバーによって処理される可能性があります(例:POSTリクエスト)。しかし、CORSヘッダーが存在しない限り、ブラウザはJavaScriptがその結果を読み取ることをブロックします。
- 強制力を持つポイント: CORSは完全にブラウザによって強制されます。そのため、
curlや Postman のようなブラウザ以外のクライアント(これらはCORSヘッダーを完全に無視します)によってアクセスされるサーバーには、全く保護を提供しません。
なぜ開発者はCORSに苦戦するのか
開発者の間での技術的な議論では、CORSがなぜ根続的な混乱の要因となっているのか、いくつかの理由が挙げられます:
逆転したセキュリティモデル: ほとんどのセキュリティモデルは、サーバーがアクセスの裁定者であると想定しています。CORSでは、ブラウザが裁定者となり、サーバーとユーザーを保護するために、悪意のあるコードから保護します。
見えない脅威モデル: CORSが防ぐ脅威は、開発中にはしばしば仮説的なものに過ぎません。開発者は、コードを動かすためにCORSを「厄介な」エラーメッセージとしてのみ遭遇するため、コードを動かすことだけに集中し、安全でないデフォルト(例:
Access-Control-Allow-Origin: *)を使用してしまいがちです。エラーメッセージの不備: ブラウザのCORS失敗時のエラーメッセージは、セキュリティ上の理由から意図的に曖昧にされていることが多く、デバッグが難しく、他のネットワークエラーとの区別が困難です。
"CORSは[セキュリティのデフォルトの形]とは本質的に異なっているため... その概念を理解するには、短時間であっても注意深く読む必要があります。アプリケーションのコードを書くことに集中しすぎている開発者は、立ち止まって確認することを望まないかもしれません。"
CORSの問題を避けるためのベストプラクティス
- リバースプロキシを使用する: バックエンドをフロントエンドと同じオリジンでホストすること(例:リバースプロキシ経由)により、標準的なSOPに従うことで、CORSの必要性を完全に排除できます。
- localhostでのハックを避ける: 特権的な操作のために、CORSを回避するために画像やスクリプトタグを使用しないでください。ローカルサーバーが必要な場合は、適切なCORSヘッダーを実装し、サーバー側でリクエストのオリジンジンを検証してください。
- 攻撃者のように考える: サーバーに到達するあらゆるリクエストが、潜在的に悪意のあるものである可能性があることを認識してください。CORSは、リクエストの実行を防止するために依存しないでください。すべてのリクエストに対して、適切な認証と認可のトークンを使用してください。