文章摘要
许多开发者对CORS的工作原理存在误解。文章以Zoom漏洞为例,指出开发者常通过加载本地图片等方式规避CORS限制,这种做法存在安全隐患。
文章总结
开发者对CORS的理解不足
2019年7月10日 — 克里斯·福斯特
在全栈咨询工作中,我有机会与来自不同规模和行业公司的众多开发者合作,这让我观察到一些普遍存在的难题。近期一个常见且相关的问题是:太多Web开发者不了解CORS的工作原理。
这一点因最近的Zoom漏洞而显得尤为及时。安全研究员乔纳森·莱楚发现,Zoom在本地机器上运行了一个监听http://localhost:19421的Web服务器。当用户加载Zoom链接时,Zoom网站会向该本地服务器发送请求,指示其打开原生Zoom应用。尽管整篇文章值得一读,但以下内容引起了我的注意:
我还发现,该页面并未使用常规的AJAX请求,而是从本地运行的Zoom Web服务器加载一张图片。图片的不同尺寸对应服务器的错误/状态码。你可以在此处看到相应的逻辑判断。
我提出的一个问题是:为什么这个Web服务器要将数据编码在图片的尺寸中?原因是为了绕过跨域资源共享(CORS)。出于非常明确的原因,浏览器会明确忽略针对本地主机服务器的CORS策略。
最后一句并不正确——Chrome确实会尊重本地主机Web服务器的CORS头。作为Web开发者,你可能在使用Create React App时遇到过这种情况:前端应用在一个端口,后端API在另一个端口。你的应用正在对本地主机进行跨域请求,所有浏览器都支持这一操作。
这表明Zoom可能急于推出该功能,却未能理解CORS。他们无法在不被浏览器阻止的情况下发起AJAX请求,于是构建了这种图片技巧来绕过CORS。然而,这种做法带来了严重漏洞:不仅Zoom网站可以触发原生客户端操作并获取响应,互联网上的任何其他网站也能做到。
那么,一个安全的实现应该是什么样的?监听localhost:19421的Web服务器应实现REST API,并设置值为https://zoom.us的Access-Control-Allow-Origin头。这将确保只有运行在zoom.us域上的JavaScript能与本地服务器通信。此外,为防止页面在后台自动打开Zoom会议,zoom.us应使用内容安全策略(CSP)头,阻止在iframe中渲染页面。
这仍存在漏洞:任何页面都可能将你的浏览器重定向到意外的zoom.us会议链接。但这更多是Zoom的用户体验决策,而非软件漏洞。我个人认为这种做法也有问题。他们声称通过直接打开应用来改善用户体验,但良好用户体验设计的原则之一是软件必须可预测。
如果我点击一个链接,我期望它不会突然让陌生人访问我的摄像头和麦克风。Zoom打破了这一期望。即使出于用户体验原因不想使用内置浏览器弹窗,也应在应用内实现弹窗!Google Meet在这方面做得很好:
我不想偏离本文对CORS的讨论重点。无论用户体验方面的争论如何,在本地主机上运行Web服务器本身就是一项冒险行为。它绝对不应向互联网上的每个网站提供特权功能访问,例如安装软件。CORS能让你安全地实现这一点——不要绕过它!
我无法确定Zoom是否因不理解CORS而采用了这种方式。但据我所知,没有人能找到任何合理理由来解释他们现有的做法。在Reddit上,有用户指出Firefox可能阻止从安全源到非安全源的XHR请求,这或许能解释其动机。然而,当源为本地主机时,Firefox是支持这一点的。此外,原生应用可以生成唯一的自签名证书,或使用浏览器扩展。在任何情况下,这都不是忽略源过滤的合理理由。
不仅仅是Zoom。据我所知,许多开发者并不清楚CORS的工作原理。Stack Overflow上也有大量相关问题。不幸的是,这些问题常伴随着推荐不安全默认设置的页面,例如Express中的某个示例,若直接复制会导致应用易受攻击。其他供应商也曾被发现存在与Zoom完全相同的漏洞。
开发者只想让代码运行起来,完全绕过同源策略或许能实现目标,但当有人发现你的做法时,就会像Zoom现在这样陷入麻烦。
我见过经验丰富和新手开发者都对CORS感到困惑。是CORS API过于复杂和令人困惑,还是我们只需要围绕CORS和CSP等问题提供更好的开发者教育?我不确定,但当前的方法显然效果不佳。
评论总结
根据评论内容,总结如下:
主要观点一:CORS难以理解,调试困难 - 多位开发者反映CORS调试耗时且错误信息不明确。piyh指出:“The only thing I remember about CORS is that it takes way longer than expected to debug, by design the error messages sent to the browser are intentionally gutted”(CORS调试时间远超预期,浏览器错误信息被刻意简化)。 - deathanatos补充:“When I help others debug these, generally I find there is little expectation of what the preflight 'should' be”(帮助他人调试时,发现大家对预检请求缺乏预期)。
主要观点二:威胁模型反直觉,与常规安全思维相悖 - frogulis分析:“CORS is somehow inverted from the default 'shape' of security in web dev”(CORS与Web开发中默认的安全形态相反)。 - somat进一步解释:“CORS is threat model used for when you can't trust your self”(CORS是用于不信任自身时的威胁模型)。 - encomiast指出:“Many developers don't really understand the threat model”(许多开发者不理解威胁模型)。
主要观点三:文档和实现存在缺陷 - kartoshechka批评:“cors docs are written either from solution or implementation point of view, not the 'why this exists'”(CORS文档从解决方案或实现角度编写,而非解释存在原因)。 - preommr认为:“it's a patchwork of compromises due to legacy issues, rampant inconsistencies”(它是遗留问题、不一致性和过度设计的妥协产物)。
主要观点四:实际应用中的问题 - robertclaus指出:“there's an awful lot of servers out there that will happily take CORS requests from any host”(大量服务器接受来自任何主机的CORS请求)。 - ottoflux观察到:“the amount of code i've seen either allowing * when it shouldn't”(大量代码错误地允许通配符*)。 - muvlon纠正常见误解:“CORS doesn't restrict anything, it loosens the default set of restrictions”(CORS不限制任何内容,而是放宽默认限制)。
主要观点五:部分开发者认为可借助AI解决 - rishabhpoddar认为:“developers don't need to understand CORS anymore.. cause claude / gpt does”(开发者不再需要理解CORS,因为AI可以解决)。 - mock-possum表示:“I make Claude fix it for me”(让Claude修复CORS问题)。
平衡观点: - 部分开发者认为CORS本质简单,只需花时间理解。supriyo-biswas推荐MDN文档:“I wish more people read the CORS article on MDN”(希望更多人阅读MDN的CORS文章)。 - 也有观点认为CORS在现有架构中作用有限,dboreham直言:“The only thing to understand is that it does nothing useful today”(唯一需要理解的是它今天毫无用处)。