2019 年 Zoom 那个漏洞,最刺眼的细节不是客户端在本机起了一个 Web Server。

真正反常的是:Zoom 为了让网页和本地服务通信,用了一张“图片”的宽高来传状态。

Zoom 客户端会监听 http://localhost:19421。用户点击会议链接后,网页请求这个本地服务,再唤起原生客户端。正常做法应该走受浏览器安全模型约束的接口。Zoom 当时的方案更绕:让本地服务返回图片,用图片尺寸编码状态码,避开 AJAX 读取时会遇到的 CORS 限制。

这不是单纯的代码失误。它更像一次工程激励的暴露:功能要顺,体验要无感,边界就被当成麻烦。

这个漏洞真正绕过了什么

把事实压短看:

问题事实影响
本地服务Zoom 监听 http://localhost:19421浏览器网页可以碰到本机客户端能力
通信方式用图片宽高传状态避开 AJAX/CORS 的跨源读取限制
常见误解以为 localhost 天然不受 CORS 管Chrome 等浏览器会尊重 localhost 服务返回的 CORS header
风险边界不只 zoom.us 能触发这条链路任意网站都有机会把用户带进唤起客户端的流程

这里最容易讲错。

CORS 不是服务器鉴权。它不能证明用户是谁,也不能替代登录态、token、权限校验。

CORS 主要限制的是:浏览器里的跨源读取。很多跨源请求本来就可能发出去,关键在于浏览器是否允许调用方读到响应。把 CORS 当成“服务器安全认证机制”,是另一种误解。

但它也不是可有可无的前端报错。

如果 Zoom 的本地服务只允许:

Access-Control-Allow-Origin: https://zoom.us

那至少能把读取本地特权接口响应的网页限制在 Zoom 自家域名下,而不是让全互联网都能轻松参与这条链路。

再加一层,Zoom 页面可以通过 CSP 禁止被 iframe 渲染,降低网页在后台自动打开会议的风险。这个措施不能解决一切。任意页面仍然可以跳转到 zoom.us 的会议链接。那一部分更接近产品体验决策,而不只是安全漏洞。

Google Meet 的会前确认体验就提供了一个对照。点链接后,用户能看到要进哪个会议、摄像头和麦克风是什么状态。它不炫技,但给了用户可预测性。

可预测性本身就是安全体验的一部分。

受影响的不是 Zoom 一家

我不愿意断言 Zoom 当时一定是不懂 CORS。原作者也说过,无法确认团队动机。

真实情况可能更复杂:兼容性、上线压力、历史包袱、临时方案变成正式架构。工程现场常常就是这样。没人宣布要破坏安全边界,但每个人都在为“先跑起来”让一步。

问题是,边界一旦让出去,漏洞迟早会来收账。

对不同角色,这件事的提醒不一样:

对象该调整什么不该怎么做
Web 前后端开发者遇到 CORS 报错时,先确认 origin、credential、读取权限和接口意图直接把 Access-Control-Allow-Origin 改成 *
技术负责人把 localhost 服务、本地助手、客户端唤起流程纳入安全评审只验收“链接能打开、客户端能拉起”
安全工程师重点看本地特权接口是否限制来源、是否需要用户确认、是否能被 iframe/跳转链滥用只扫远程 API,忽略本机端口

这类风险不只属于会议软件。

很多本地助手、开发工具、硬件管理器、浏览器扩展配套服务,都喜欢在 localhost 起一个服务,让网页调用本机能力。这个模式本身不是原罪。问题在于,localhost 不是魔法安全区。

它只是用户机器上的一扇门。

门后如果连着摄像头、麦克风、安装器、客户端唤起、本地状态读取,就不能靠“正常网站才会访问”来防守。

Stack Overflow 和各类框架示例还放大了这个问题。很多答案教你用 * 解决 CORS。很多示例代码默认只追求请求成功。复制一下,页面不红了,工单关闭了。

“天下熙熙,皆为利来。”放到工程组织里,就是指标奖励交付,很少奖励边界清楚。

理解 CORS、CSP、origin、credential,短期看不到功能增量。绕过去,马上有结果。组织会自然偏向后者。

接下来要看的不是补丁,而是默认值

安全实现当然要从具体动作开始。

限制 Access-Control-Allow-Origin。不要把本地特权接口暴露给任意网页。该有用户确认的地方,就别为了无感体验偷偷跳过。涉及 iframe、自动唤起、后台打开会议的流程,也要配合 CSP 和产品层确认。

但我更在意默认值。

一个团队如果默认把浏览器同源策略看成障碍,就会不断发明捷径:图片尺寸传状态、JSONP 式回魂、全域名放行、临时白名单永不下线。

这些方案都很像“聪明工程”。问题是,安全事故常常就长在这种聪明里。

接下来判断类似产品,别只看它有没有本地服务。更要看三件事:

  • 本地服务是否只信任明确来源,而不是默认信任所有网页。
  • 高权限动作是否需要用户可见确认,而不是一跳到底。
  • 示例代码、SDK、文档默认值是否安全,而不是把风险留给使用者自己补。

这里有现实约束。浏览器安全模型不算好懂,CORS 的开发体验也不优雅。本地客户端和网页之间的打通,确实常常背着兼容性压力。

但复杂不是绕开的理由。

越靠近摄像头、麦克风、安装器、本机客户端这些高权限能力,越不能把用户的确认感当成体验噪音。产品越想无感,安全越需要有感。

Zoom 这件事的价值,不是多年后翻旧账。它提醒的是下一次。

当你再看到 CORS 报错,别急着把 * 贴上去。那不是浏览器刁难你。那可能是它最后一次替你挡住事故。