2 ポイント 投稿者 GN⁺ 4 시간 전 | 1件のコメント | WhatsAppで共有
  • ZoomのローカルWebサーバーの脆弱性は、多くのWeb開発者が CORSの動作方式 を誤解しているとき、セキュリティ境界がいかに簡単に崩れるかを示している
  • Zoomは localhost:19421 のローカルサーバーと通信しながら、AJAXの代わりに 画像サイズ でステータスコードを伝えており、これはCORSを回避しようとする迂回実装と解釈できる
  • ChromeはlocalhostのWebサーバーにも CORSヘッダー を適用し、異なるlocalhostポート間のフロントエンド・バックエンド通信もブラウザでサポートされている
  • より安全な設計は、ローカルサーバーがREST APIを提供し、Access-Control-Allow-Origin を設定して zoom.usのJavaScript だけがアクセスできるよう制限する方式である
  • 同一生成元ポリシーを回避すればコードは動くかもしれないが、ローカルサーバーの権限ある機能がインターネット上のあらゆるWebサイトに公開されうる

ZoomのローカルWebサーバーが生んだCORS回避

  • フルスタックコンサルティングの現場でさまざまな規模や業界の開発者に接する中で、Web開発者が CORS を理解していない問題が繰り返し見られた
  • 最近のZoom脆弱性で、セキュリティ研究者Jonathan Leitschuhは、Zoomがユーザーのマシン上で http://localhost:19421 Webサーバーを起動していることを発見した
    • ユーザーがZoomリンクを開くと、ZoomのWebサイトがlocalhost Webサーバーにリクエストを送り、ネイティブのZoomアプリを実行する
    • 通常のAJAXリクエストの代わりに、ローカルのZoom Webサーバーから画像を読み込み、画像の異なるサイズでサーバーのエラー・ステータスコードを表現していた
  • ブラウザがlocalhostサーバーのCORSポリシーを無視するという解釈は誤りであり、Chromeはlocalhost Webサーバーの CORSヘッダーを尊重 する
    • Create React AppのフロントエンドとバックエンドAPIを異なるlocalhostポートで実行するときもクロスオリジンリクエストが発生し、これはすべてのブラウザでサポートされている
  • AJAXリクエストがブロックされたため、Zoomは画像ハックでCORSを回避したように見える
    • その結果、ZoomのWebサイトだけでなく、インターネット上の他のWebサイトもネイティブクライアントの動作をトリガーし、応答にアクセスできるようになった

安全な代替策と残るUXの問題

  • 安全な実装は、localhost:19421 のWebサーバーが REST API を実装し、Access-Control-Allow-Origin ヘッダーの値を https://zoom.us に設定する方式である
    • こうすれば、zoom.usドメインで実行されるJavaScriptだけがlocalhost Webサーバーと通信できる
    • zoom.usは、バックグラウンドでZoomミーティングが自動的に開くのを防ぐため、iframeレンダリングをブロックする Content Security Policy ヘッダーを置くこともできる
  • どのページでもブラウザをzoom.usのミーティングリンクへリダイレクトできる問題は依然として残る
    • ただし、これはソフトウェアの脆弱性というより、Zoomが選んだ ユーザー体験 に近い
    • リンクをクリックしたとき、カメラとマイクが見知らぬ相手に突然開かれないだろうというユーザーの期待をZoomは裏切っている
    • ブラウザ標準のポップアップをUX上の理由で避けたいなら、アプリ内でポップアップを表示することもでき、Google Meetはその方式をうまく使っている
  • localhostでWebサーバーを実行すること自体が危険な試みであり、特に ソフトウェアのインストール のような権限ある機能をインターネット上のあらゆるWebサイトに提供してはならない
    • CORSはこうした状況を安全に処理するための仕組みであり、回避すべきではない

Zoomだけの失敗ではないCORSの混乱

  • Zoomが実際にCORSを理解していなかったためにこの方式を選んだのかは確かではない
    • Redditの lerunicorn は、Firefoxが安全なオリジンから安全でないオリジンへのXHRをブロックする可能性があると見ている
    • しかしFirefoxは、originがlocalhostの場合にはこれをサポートしている
    • ネイティブアプリは固有の自己署名証明書を生成でき、ブラウザ拡張 を使うこともできる
    • いずれの場合でも、オリジンのフィルタリング を省略する正当な理由にはならない
  • CORSの混乱はZoomだけの問題ではない
    • Stack Overflowには Access-Control-Allow-Origin 関連の質問が 数多く存在 する
    • Expressの例の中には、そのままコピーするとアプリケーションが脆弱になりうる 安全でないデフォルト を勧めるページもある
    • 他のベンダーもZoomと 同じ脆弱性 を経験したことがある
  • 開発者はコードを動かしたいと考えるが、同一生成元ポリシーを丸ごと回避すると、Zoomの事例のようにローカル権限が外部のWebサイトに露出する
  • CORSの混乱は熟練開発者にも新人開発者にも見られ、CORS APIが過度に複雑なのか、あるいはCORSとCSPの教育が不足しているのかは明らかではないが、現在のやり方はうまく機能していない

1件のコメント

 
GN⁺ 4 시간 전
Hacker Newsのコメント
  • TFAもCORSを正しく理解していないか、かなりひどく誤って説明しているように見える
    Access-Control-Allow-Origin: https://zoom.us は、zoom.us ドメインの JavaScript だけが localhost サーバーと通信できることを保証するものではない。他のウェブサイトの JavaScript も localhost:19421 に同じようにリクエストを送ることはできる。CORS は何かを制限するものではなく、既定の制限を緩和する仕組みだ。このヘッダーがするのは、zoom.us で実行される JavaScript が localhost:19421 のレスポンスを読めるようにすることだけであり、リクエスト自体はどうせ発生するのだから、バックエンド側で副作用が起きないようにしなければならない

    • これがなぜ最多得票コメントなのかわからない。OP が正しく、上の説明は間違っている
      GET リクエストは送られるが、本来は冪等であるべきなので、サーバーが正しく実装されていれば副作用を起こせないはずであり、GET ではレスポンスを読めるかどうかが重要だ。逆に、副作用を持ちうる非冪等リクエストでは、クロスオリジンの状況で実際のリクエストの前にまず preflight OPTIONS リクエストが送られ、OPTIONS レスポンスに正しいヘッダーがなければ実際のリクエストは送信されない
    • CORS がそういう役割を果たすと言うこともできないと思う
      CORS に関する誤解があまりにも広く распространし、文書もたびたび互いに矛盾しているので、見知らぬ相手が正しく実装していると期待するのは難しい。あるプロトコルがこれほど広範な混乱を生むなら、一方が正しく動いていても他方もそうかはわからない。人々が別の実装に合わせてコードが動くまで修正してきたのなら、間違っているのが自分側なのか相手側なのかも曖昧になる
    • 私の理解では、preflight OPTIONS の主な目的は、本来許可されない HTTP リクエストを防ぐことであり、もともと許可されているリクエストについては CORS は何もしない
      たとえば Content-Typetext/json の POST は、OPTIONS preflight なしでは第三者ホストに送れないが、multipart/form-data の POST は許可されており、CORS では防がれない。そしてエンドポイントが Content-Type を厳密に確認せず JSON だと仮定しているなら、どのウェブサイトからでもユーザー操作なしに POST を送れることになる
    • 「安全なメソッドだけを話していると仮定する」は、かなり大きな仮定だ
      まともなウェブ開発者なら GET/HEAD/OPTIONS で状態を変更させてはいけないし、会議への参加のようなものは状態変更だ。PUT/DELETE も冪等であるべきだ。JSON またはフォーム以外の形式の POST API は Content-Type ヘッダーを確認すべきであり、PUT/PATCH/DELETE とフォーム形式でない Content-Type の POST は preflight を引き起こし、実際のリクエストがサーバーに到達する前に CORS が確認される
    • 記事の「ネイティブアプリは固有の自己署名証明書を生成できる」という部分にも問題がある
      証明書を作るだけでは動作せず、マシン上のすべてのブラウザ信頼ストアにルート CA 証明書としてインストールされていなければならない。ルート CA の秘密鍵が適切に保護されていなければ、どのウェブサイトでも中間者攻撃ができてしまうので、少なくとも名前制約が必要だ(https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10)。ところが Chrome では 2023 年の v112 以前まで、ルート CA ではこれが機能せず(https://alexsci.com/blog/name-non-constraint/)、そのため中間 CA を追加してそこに制約をかける必要があった。もちろんルート CA の鍵は破棄するのが正しい
      以前、ローカルのルート CA を使うプロジェクトで基本制約を追加したことがあるが、ルート CA に誤って入れてしまい、しかも全ブラウザでテストしていなかった
  • もっと多くの人が MDN のCORS ドキュメントを読めばいいのにと思う。CORS を理解しようとするときにとても役立ったし、ここのコメントを見るまで人々がここまで苦労しているとは知らなかった
    https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS

    • あのドキュメントひとつで、単純なオリジンの例だけでなく、preflight の動作方式まで大半の答えが得られる
  • 理解が難しいのは CORS だけではなく、多くの開発者が脅威モデルをきちんと理解していない
    説明を聞いても、なぜそれが大きな問題なのか直感的につかめないことが多い。特にバックエンド開発者が CORS を設定することが多いが、CORS はアクセス権限を保護する仕組みではないので、バックエンドの立場からはあまり重要に見えない。攻撃者は持ち出せないと感じるし、フロントエンドの立場では面倒な障害物のように見えやすい。この記事は具体例をうまく示している

    • 同じ開発者がフロントエンドとバックエンドの両方を書いたプロジェクトでも CORS 設定を間違えていたことがある
      運用担当としてロードバランサーであらためて正しく直し、少なくともアプリケーションはいまは動いている。CORS は理解しにくいが、CORS が防ごうとしている脅威モデルだけでなく、ウェブ開発全般、とくに HTTP プロトコルを理解していない開発者も多いというのがさらに残念だ
    • CORS の脅威モデルはそれほど難しくない。攻撃者がユーザーを自分のサイトに誘導し、あなたのサイトで何らかの行動をさせる状況のことだ
    • CORS はかなり奇妙な既定の権限モデルの上に積み重なっているので混乱しやすい。multipart/form-data はよいのに、アプリケーションの JavaScript はだめ、というようなものだ
    • 攻撃者と防御者の観点から見ると、脅威モデルはそれほど自然ではない
      CORS は任意であり、他のライブラリやツールは単に無視することもできる。CORS が実際に意味を持つのは、ログイン済みの人間ユーザーに対する XSS と CSRF を防ぐ場合だけで、それ以外の攻撃シナリオではどうせ HTTP ヘッダーを偽装するスクリプトやプログラムを使うので無意味だ。だから人々は結局すべての CORS オプションを有効にしてしまうのだが、これは XSS と CSRF を許してしまう最悪のケースだ
    • CORS は、人々が帯域幅やホスティング資源を簡単にただ乗りできないようにするには非常に優れている。盗もうとするなら自前でプロキシを立てる必要があり、そうなれば遮断しやすくなる
  • このコメント欄は本当に情報レベルが低く見えるし、むしろ筆者の要点をそのまま証明している

    • 世代差なのかもしれない
      CORSが生まれる前にWeb開発をしていたなら、もともとクロスドメインリクエストは禁止されていて、CORSはこの制約を回避するために生まれたことを理解している。だから、やりたいことを実現するにはCORSを有効にすればいいと受け止めやすい。
      逆にCORS以後にWeb開発を学んだ人は、クロスオリジンリクエストを試し、ブラウザが許可されていないと判断し、CORS preflightを試し、失敗するとコンソールにCORSエラーが出る、という流れしか見ない。内部動作を知らずドキュメントも読まないまま推測すると、CORSがリクエストを止める原因だと思い込み、「CORSを無効化」しようとするようになる。だが、CORSは問題の原因ではなく解決策だ。
      同じ誤解を持つ人たちがチュートリアルやオンライン議論で自信満々に繰り返すので、さらに混乱する
    • CORSは直感的ではないが、ドキュメントを読めば理解できる
      https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
  • コメントを読んで、自分だけではないと確認できた。誰もCORSを理解できない理由は、複雑すぎて衝突が多いからだ。
    標準やヘッダーも変わり続けるので、開発者はたいてい動くまであれこれ触ってから製品をデプロイして終わる。動いていても開発者コンソールにエラーや警告が残ることがあるが、表向き問題なく動いていればそのまま触らなくなる

  • CORSを理解するには、まず同一オリジンポリシーを理解する必要がある
    特に「なぜこれが必要なのか?」が難しいなら、ここから始めるとよい: https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Same-origin_policy
    以前、同一オリジンポリシーを面接質問に使ったことがあるが、多くの応募者がなじみがなく、その質問から得られる情報は少なかった

    • フロントエンド開発者を採用するなら、かなりよい質問だと思う
      Webアプリを開発してきたなら、いつかは同一オリジンポリシーに直面していたはずだからだ。知らないなら、たとえばバックエンドとどう通信していたのかなどをさらに聞くことになる。CORSの問題に遭遇したが最短の回避策だけ適用して忘れたのか、実際に理解しようとしたのかも、一部の役割では有用なシグナルになる。
      バックエンド職にはあまり向いていない。すべてのバックエンド開発者が、CORSの問題に頻繁に遭遇するフロントエンドチームと密接に働いていたわけではないからだ
  • CORSについて覚えているのは、デバッグに予想よりはるかに時間がかかり、ブラウザのエラーメッセージが意図的に乏しく、CORSエラーがほかの失敗モードと最初は区別しにくいということだ

    • CORSエラーは「ブラウザに送信されたエラーメッセージ」ではなく、ブラウザがリクエストを許可できないと判断して生成したエラーだ。
      もちろん、サーバーがCORSリクエストを理解できずおかしな応答を返すと、それが最終的にCORS失敗として表れることはある
  • コメント欄を見ていてかなり面白かったので付け加えると、同一オリジンポリシーはブラウザがアクセス権のないWebサイトへ情報を漏えいさせないよう保護し、CORSはその保護を弱められるようにする
    たとえば同一オリジンポリシーは、example.comyoutube.com の購読リストを取得できないようにする。だがCORSを使えば、example.comyoutube.com/public/* にはアクセスできるよう許可できる。
    もう一つの用途として、バックエンドAPIが別のフロントエンドの配下で動いてデータ窃取につながるのを防ぐ効果もある。たとえば実際のサービスにはログインしているが、ユーザーは g00gle.com にいて、すべてのリクエストが中間者攻撃されうる状況を防げる

    • 正確には逆だ。こうしたセキュリティ問題を防ぐのはSOPであり、CORSはSOPを緩めて、より複雑なアプリケーション間の動作を許可するための機能だ
  • 自分もその一人だ。CORSは定期的に勉強し直さなければならない話題で、いつも忘れてしまい頭に定着しない。
    バックエンド開発者なのでCORSの問題にほとんど遭遇しないからだと思う。毎日使わないものは忘れやすい

    • CORSとCSPの開発者体験はひどい。ブラウザが問題の発生源をまともに教えてくれないからだ。
      まともな世界なら、エラーメッセージに「レスポンスヘッダー」や「metaタグ」といったヒントが入っているはずだが、主要ブラウザベンダーは謎めいたメッセージを書く人たちを雇っているように見える。Chromeの “requested resource” はまだましだが、それでも暗号のようだ。
      より良いメッセージは、たとえば https://bank.com のリソースがCORSヘッダーを持たないためクロスオリジンリクエストを許可しないとか、現在のオリジンがCORS許可リストに含まれていない、といったものになるべきだ。ネットワークタブのpreflightリクエストやMDNへのリンクも併せて表示すべきだろう。CSPも、このページのCSPヘッダーのためリソースを取得できず、インスペクタのページリクエストヘッダーやmetaタグへつなげる形のほうがよい
    • CORSの最大の問題は、ほとんどのエラーがフロントエンドの問題、特にブラウザの問題のように見えるのに、実際の修正はバックエンドで行わなければならない点だ
    • 自分も似た感覚がある。何度かCORSを扱わなければならなかった状況は、「このサーバーから何かを取得しなければならないが、そのサーバーのCORSやCSPは変えられない」という要求だった。セキュリティ用語で言えば、「セキュリティシステムがあるが回避しなければならない」という意味だ。
      結局のところ、たいていはサーバーが改変されていないブラウザリクエストからのみアクセスされるという想定に依存している。Zoomの脆弱性は、クライアント側でCORSやCSPを回避するのがあまりに簡単だったために生じたし、Zoomが悪く、怠慢で、愚かだったのはその通りだが、こうしたモデルを維持し続けるコミュニティにも責任があると感じる
  • 同一オリジンポリシーが、ブラウザが悪意あるスクリプトを実行して情報を漏えいさせるのをどう防ぐのかは理解している。Access-Control-Allow-Origin ヘッダーでサーバーが追加のオリジンを信頼すると宣言し、SOPを緩和することも理解している。
    それでも Access-Control-Allow-Headers ヘッダーの目的はまだ分からない。ブラウザのセキュリティを改善するようにも見えないし、ましてサーバーのセキュリティでもない。プロトコル設計者が「完成度のために」入れたのか気になる。関連: https://stackoverflow.com/questions/17992042