世界を動かす技術を、日本語で。

開発者はCORSを理解していない (2019)

2026年6月21日原文(fosterelli.co)

概要

  • CORS (クロスオリジンリソースシェアリング)に関する開発者の理解不足
  • Zoom のCORS回避実装が重大な脆弱性を生んだ事例
  • 正しいCORS設定 の重要性と推奨される対応策
  • 開発現場での CORS誤解 の広がりと教育不足への懸念
  • ソフトウェアの セキュリティとユーザー体験 のバランス問題

開発者が理解していないCORS

  • フルスタックコンサルティング で多様な開発者と協業する中で、 CORSの理解不足 が普遍的な課題であることを実感
  • Zoomの脆弱性事件 を例に、CORSの誤解がどのような問題を引き起こすかを解説
  • Zoomは localhost:19421 でWebサーバーを動かし、 画像の寸法でエラーやステータスを伝達 する仕組みを採用
  • これは CORSを回避 するための手法であり、 AJAXリクエスト ができないという誤解から生まれた設計
  • Chromeなどの主要ブラウザ はlocalhostへのCORSヘッダーを正しく処理するため、この回避策は不要

ZoomのCORS回避策が生んだリスク

  • 画像読み込みによるCORS回避 は、結果として 全てのWebサイト からZoomのネイティブクライアント操作が可能になる重大な脆弱性につながる
  • セキュリティ上、 localhostで動作するWebサーバー はアクセス制限が必要
  • Zoomの実装では どのドメインからでも操作可能 となり、セキュリティリスクが拡大

安全な実装方法と推奨策

  • localhost:19421 で稼働するWebサーバーは REST API を提供し、 Access-Control-Allow-Origin: https://zoom.us ヘッダーを設定するべき
  • これによって zoom.usドメインのJavaScriptのみが通信可能 となり、他サイトからの不正アクセスを防止
  • Content Security Policy(CSP) ヘッダーでiframe内レンダリングを禁止し、さらなる攻撃を防ぐ
  • ユーザー体験 の観点でも、予期しないカメラ・マイク起動は避けるべきであり、 Google Meet のようにアプリ内での明示的なポップアップ表示が望ましい

CORSに対する誤解と教育の必要性

  • Zoomだけでなく、多くの開発者が CORSの仕組みを正しく理解していない 現状
  • Stack Overflow などの情報源でも、 危険なデフォルト設定 が推奨されている例が多い
  • 他ベンダー でも同様の脆弱性が報告されており、 CORSの誤用 が業界全体の課題
  • 開発者は 動作優先 でCORS制約を無効化しがちだが、 セキュリティリスク を十分に認識すべき
  • 経験豊富な開発者 でも混乱しているケースが多く、 CORSやCSPに関する教育の強化 が必要

CORS APIの複雑さと今後の課題

  • CORSやCSPの仕様が 複雑で分かりづらい ことが混乱の一因
  • 開発者教育の充実 や、 より安全なデフォルト設定 の普及が今後の課題
  • 現状のアプローチでは 安全性確保が十分でない ため、業界全体での意識改革が求められる

Hackerたちの意見

CORSについて覚えてることと言えば、デバッグに予想以上に時間がかかるってことかな。ブラウザに送られるエラーメッセージが意図的に削られてるから、CORSエラーのシナリオは他の失敗モードと見分けがつきにくいんだよね。

ブラウザに送られるエラーメッセージが意図的に削られてる CORSエラーは「ブラウザに送られるエラーメッセージ」じゃなくて、ブラウザがリクエストを許可できないと判断した結果生成されるエラーなんだよね。(もちろん、サーバーがCORSリクエストを理解できずに変なレスポンスを返すこともあって、それがCORSの失敗に繋がることもあるけど。)

時々、自分が本当に「理解している」ことが何なのか分からなくなる。Zoomみたいに何億人も使ってる製品に関わってるシニアエンジニアたちがこんな問題に直面してるのを見ると、ちょっと考えちゃうよね。だから、いつも先輩が書いたコードをそのまま流用してるだけなんだ。でも、自分の仕事の領域が実はすごく抽象的だってことに気づいてる。

CORSだけじゃなくて、理解するのが難しいのは他にもあるよ。多くの(ほとんどの?)開発者が脅威モデルを本当に理解してないし、説明されてもそれが大事なことだって納得しにくい。これには、バックエンドの開発者がCORSを設定しなきゃいけないってことも関係してるし、アクセス権の保護じゃないから、バックエンドの視点からはあんまり重要じゃないように見える。悪者はそれを取得できないし、フロントエンドから見ると面倒なだけ。この記事は具体的な例を挙げてうまく説明してるね。

それに、攻撃者と防御者の視点から見ると、あんまり意味のある脅威モデルじゃないんだよね。オプションだから、他のライブラリやツールが平然と無視することもあるし。CORSは実際にはXSSやCSRFに対して、アクティブにログインしている人間のユーザーに対してだけ存在するんだ。他の攻撃シナリオはHTTPヘッダーを偽装するスクリプトやプログラムを使うから、CORSの他の部分は全く意味がない。あるブラウザが「互換性のためにユーザーエージェントは常にWindows 10であるべきだ」と決めたから、Sec-CH(クライアントヒント)ヘッダーと同じくらい無意味なんだよね。だから、みんなCORSのオプションを全部有効にしちゃうんだ。これがXSSやCSRFを許す最悪のケースなのに。多くのウェブサイトには、少なくとも埋め込まれたスクリプトがフィルタリングされていない画像(PNGとか)など、ユーザーが編集できるコンテンツがあるし。

理解するのはそんなに難しくないよ… CORSの脅威モデルでは、攻撃者があなたのユーザーの一人を誘導して、あなたのサイトでアクションを取らせるって感じなんだ。

CORSは、他の人が簡単にあなたの帯域幅やホスティングリソースを盗むのを防ぐためには素晴らしいよ。泥棒は自分のプロキシを立てなきゃいけないから、簡単にブロックできるんだ。

同じ開発者がフロントエンドとバックエンドを書いたプロジェクトがあったけど、それでもCORSを間違えてたよ。運用チームとして、ロードバランサーで正しく書き直したんだけど…まあ、少なくともアプリケーションは今は動いてる。CORSは本当に理解するのが難しいけど、残念ながらCORSが守る脅威モデルを理解できていない開発者がたくさんいるし、ウェブ開発全般、特にHTTPプロトコルも理解していない人が多いのが不思議だね。彼らはネイティブアプリもできないし。

もっと多くの人がMDNのCORSの記事を読んでほしいな。あれは当時、理解しようとしてた時にすごく助けになった。CORSに苦労してる人がいるのは知ってたけど、ここでのコメントを見る限り、こんなにひどいとは思わなかった。 [1] https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/COR...

TFAもCORSを理解してないみたいだね。もしくは、ひどく誤解してる。> localhost:19421で待ち受けているウェブサーバーはREST APIを実装し、Access-Control-Allow-Originヘッダーをhttps://zoom.usの値に設定する必要がある。これにより、zoom.usドメインで実行されているJavaScriptだけがlocalhostのウェブサーバーと通信できるようになる。いや、それはそうじゃない。別のウェブサイトからのJavaScriptもlocalhost:19421にアクセスできるから。CORSは何も制限しないで、デフォルトの制限を緩和するだけなんだ(プレフライトリクエストは無視して、単に「安全な」メソッドについて話してると仮定すると)。そのAccess-Control-Allow-Originヘッダーは、zoom.usで実行されているJavaScriptがlocalhost:19421にクエリを投げた時にレスポンスを読み取ることを許可するだけなんだ。リクエストはどちらにせよ発生するから、バックエンドでそれが悪影響を及ぼさないようにする必要があるよ。

CORSがそうだと言えるかどうかは疑問だね。CORSがどれだけ誤解されているか(矛盾したドキュメントをたくさん読んだけど、いまだによく分からない)を考えると、未知の相手が正しく実装しているとは信じられないよ。もしプロトコルがこれほど広く混乱しているなら、もう何も信じられないと思う。システムの一方が正しく動作しても、もう一方がそうとは限らないしね。人々が他の実装と動作するまでコードを適応させるなら、それは間違いだったのか、それとももう一方が間違っていたのか?

「安全な」メソッドだけを話していると仮定すると、それはかなり大きな仮定だね。まともなウェブ開発者なら、GET/HEAD/OPTIONSが状態を変更するのを許さないはず(会議に参加するのは状態を変更することだし)。さらに、PUT/DELETEも冪等であるべきだよ。JSON(または他の非フォームフォーマット)を使ったPOST APIは、コンテンツタイプヘッダーがチェックされるべきだね(text/plainのフォームはJSONボディを送れるけど、コンテンツタイプはtext/plainになる)。PUT/PATCH/DELETEや非フォームコンテンツタイプ(application/x-www-form-urlencoded、multipart/form-data、またはtext/plain)を使ったPOSTは、プリフライトをトリガーして、CORSが正しくチェックされるようになる。

Hacker Newsで議論の続きを見る