理解 CORS 和同源策略:从 Zoom 漏洞中吸取的教训
核心结论:CORS 不是服务器的安全工具
跨源资源共享 (CORS) 并不是一种防止请求到达服务器的机制;相反,它是一种浏览器端的机制,允许服务器放宽同源策略 (SOP) 的限制,以便前端应用程序可以读取响应。对这一区别的误解经常导致开发者实施“黑客手段”来绕过 CORS,或者错误地认为 CORS 头部可以保护其后端免受未经授权的请求。
案例研究:Zoom Localhost 漏洞
2019 年,Zoom 发现了一个漏洞,其中应用程序在 localhost:19421 上运行一个 Web 服务器,以允许 Zoom 网站触发原生应用。为了在向该本地服务器发送 AJAX 请求时绕过 CORS 限制,Zoom 实施了一种“图像黑客手段 (image hack)”,即将数据编码到图像文件的尺寸中。
为什么图像黑客手段会造成安全漏洞
由于浏览器通常允许跨源加载图像而没有像 AJAX 请求那样的相同限制,这种绕过方式实际上禁用了同源策略 (SOP) 提供的保护。因此,互联网上的任何网站——而不仅仅是 zoom.us——都可以向本地 Zoom 服务器发送请求,并通过图像尺寸读取响应,从而可能允许在原生客户端中执行未经授权的操作。
正确的实现方式
为了保护这一功能,Zoom 应该在 localhost 服务器上实现一个 REST API,并使用专门设置为 https://zoom.us 的 Access-Control-Allow-Origin 头部。这将允许官方 Zoom 网站读取响应,同时防止其他恶意网站这样做。此外,使用阻止 iframe 渲染的 内容安全策略 (CSP) 可以防止 Zoom 网站被嵌入到恶意框架中以触发后台操作。
SOP 与 CORS:消除困惑
开发者的许多困惑源于未能区分同源策略 (SOP) 和跨源资源共享 (CORS)。
同源策略 (SOP)
SOP 是主要的安全性边界。它防止来自一个源(协议、主机和端口)的文档读取属于另一个源的数据。例如,SOP 防止 malicious-site.com 在你登录时从 gmail.com 获取你的私人邮件。
跨源资源共享 (CORS)
CORS 是一种放宽 SOP 的机制。它允许服务器明确告诉浏览器:“我信任这个特定的源,因此你可以让运行在那里的 JavaScript 读取此请求的响应。”
关键技术区别
- 写入与读取: 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 来防止请求被执行;请为每个请求使用适当的的身份验证和授权令牌。