不安全的跨域资源共享(CORS)中的“预检请求缓存中毒”(Preflight Cache Poisoning)漏洞与防护
题目描述
这是一个关于跨域资源共享(CORS)的高阶攻击与防护题目。我们探讨的场景是:攻击者可能通过滥用CORS预检请求(Preflight Request)的缓存机制,来实施缓存投毒攻击,从而破坏同源策略的保护,导致跨域攻击的权限提升。
简单来说,CORS预检请求是浏览器在发送某些“非简单跨域请求”(如使用了Content-Type: application/json或自定义头部的请求)前,自动发送的一个OPTIONS请求,用于获取目标服务器是否允许该跨域请求。服务器和中介(如CDN、反向代理、网关)可能缓存这个OPTIONS请求的响应。如果攻击者能够操纵或污染这个缓存,就可能使得后续合法的跨域请求绕过安全检查,或者将恶意数据注入到缓存中,影响其他用户。
核心问题: 不安全的CORS配置(特别是Access-Control-Allow-*相关头部的设置)与不安全的中间件缓存策略结合,导致攻击者可以利用缓存的OPTIONS响应,为后续请求授予本不应有的跨域权限,实现如反射型CORS攻击、权限提升或数据窃取。
解题过程与细致讲解
我们将分四个步骤,循序渐进地理解这个漏洞的原理、利用条件和防御方法。
第一步:理解基础——CORS预检请求(Preflight Request)与缓存
- 为什么需要预检请求?
- 浏览器同源策略(SOP)默认阻止跨域读取某些资源。CORS是一种机制,允许服务器声明哪些“外源”(不同协议、域名、端口)可以访问其资源。
- 对于“非简单请求”(如使用
PUT、DELETE方法,或Content-Type: application/json,或携带自定义头如X-Custom-Header),浏览器会先发送一个OPTIONS方法的“预检请求”到目标服务器。 - 这个
OPTIONS请求会携带几个特殊头部,例如:Origin: 发起请求的源(如https://evil.com)。Access-Control-Request-Method: 实际请求打算使用的方法(如POST)。Access-Control-Request-Headers: 实际请求打算携带的自定义头部列表(如X-Custom-Header)。
- 服务器的响应与缓存:
- 服务器应针对
OPTIONS请求返回响应,包含关键的CORS相关头部,例如:Access-Control-Allow-Origin: https://trusted.com(或*,但不安全)。Access-Control-Allow-Methods: POST, GET, OPTIONS。Access-Control-Allow-Headers: X-Custom-Header, Content-Type。Access-Control-Max-Age: 86400(单位秒,指示浏览器可以缓存此预检响应多长时间,在此期间内对同一URL的相同跨域请求不再发送预检)。
- 关键点: 这个
OPTIONS请求的响应不仅可能被浏览器缓存(根据Access-Control-Max-Age),还可能被服务器与客户端之间的任何中间件(如CDN、反向代理、API网关)缓存,特别是当这些中间件将OPTIONS方法视为可缓存的GET请求的变体时,或者缓存配置不区分HTTP方法时。
- 服务器应针对
第二步:漏洞原理——攻击者如何“下毒”?
攻击者的目标是污染这个缓存的OPTIONS响应,使其包含有利于攻击的CORS策略。
攻击场景示例:
- 前提条件:
- 目标服务器(
api.victim.com)配置了宽松或不安全的CORS策略,例如Access-Control-Allow-Origin: *或动态反射请求中的Origin头(即Access-Control-Allow-Origin: [请求中的Origin值])。 - 在
api.victim.com前面有一个CDN或反向代理,并且它缓存OPTIONS请求的响应。其缓存键(Cache Key)可能设计不当,例如仅基于请求URL,而忽略了Origin头。
- 目标服务器(
- 攻击步骤:
- 步骤1(投毒): 攻击者从一个受其控制的恶意源(
https://evil.com)向api.victim.com发送一个精心构造的OPTIONS预检请求。- 请求头包含:
Origin: https://evil.com和Access-Control-Request-Method: POST。
- 请求头包含:
- 步骤2(服务器响应与缓存): 由于服务器配置了反射
Origin的策略,它会返回响应头:Access-Control-Allow-Origin: https://evil.com。这个响应被中间的CDN缓存了下来。由于CDN的缓存键只用了URL(如https://api.victim.com/api/data),没有包含Origin头,所以这个缓存的条目与Origin值无关。 - 步骤3(缓存命中与污染生效): 随后,一个来自合法、受信任源(
https://trusted-app.com)的用户(受害者)访问https://trusted-app.com,该页面尝试向https://api.victim.com/api/data发送一个跨域POST请求。浏览器触发预检,发送OPTIONS请求,其Origin头为https://trusted-app.com。 - 步骤4(权限错误授予): 这个
OPTIONS请求到达CDN。CDN发现其URL(https://api.victim.com/api/data)在缓存中存在一个条目(即步骤2中攻击者污染的条目),于是直接返回缓存的响应,而没有将请求转发给源服务器。这个缓存的响应中,Access-Control-Allow-Origin的值是https://evil.com,而不是https://trusted-app.com。 - 步骤5(攻击影响): 浏览器收到这个缓存的响应,发现
Access-Control-Allow-Origin(https://evil.com)与当前请求的源(https://trusted-app.com)不匹配。根据CORS规范,浏览器应该拒绝此跨域请求。然而,在某些早期的浏览器实现或特定配置下,如果服务器响应了Access-Control-Allow-Credentials: true(允许携带凭据如Cookie),且Origin为*以外的特定值,浏览器可能会错误处理。更常见的利用是,如果服务器配置为反射Origin且不验证,攻击者可以诱导用户从trusted-app.com发起请求,但由于缓存投毒,服务器响应的CORS头允许了evil.com,这可能导致后续的实际请求(如包含敏感操作的POST)被发送,且响应可能被evil.com的页面通过XMLHttpRequest读取(如果配置不当),造成敏感数据泄露。另一种影响是,污染的CORS头可能包含过长的、恶意的头部值,用于进行客户端攻击(如头注入)。
- 步骤1(投毒): 攻击者从一个受其控制的恶意源(
简单比喻: 就像一个酒店的安检口(CORS检查)。正常情况下,客人(浏览器)需要出示身份证(Origin)并登记。酒店前台(服务器)会根据名单核对。现在,攻击者提前用假身份证(evil.com)登记了一次,并且这个登记记录被粗心的保安(CDN缓存)记在了通用登记本上(只记录房间号/api/data,不记录身份证号)。当真正的客人(trusted-app.com)来登记时,保安直接翻出通用登记本,把之前假身份证的信息给了他,导致真正的客人要么被拒绝进入,要么被赋予了错误的权限。
第三步:漏洞利用的深入与变种
- 反射型CORS攻击的结合: 如果服务器直接将请求中的
Origin头值不加校验地反射到Access-Control-Allow-Origin响应头中,攻击者可以在预检请求中注入一个恶意源(如evil.com)。当这个响应被缓存后,任何后续来自其他源的预检请求都可能收到这个允许evil.com的响应,从而为evil.com打开了一个时间窗口,使其能发起跨域请求并读取响应(如果服务器还配置了Access-Control-Allow-Credentials: true,且客户端代码使用withCredentials,危险更大)。 - 缓存键操纵: 攻击的核心在于中间件的缓存键设计。如果缓存键不包括
Origin头,或者对OPTIONS方法的处理逻辑与GET相同,就容易受到攻击。攻击者可能会尝试污染不同路径、不同查询参数的缓存条目。 Vary头的缺失: 一个正确的、防御此类攻击的缓存机制,对于CORS响应,应该在响应头中包含Vary: Origin。这个头部指示缓存系统:当请求头中的Origin值不同时,应该被视为不同的资源,不能使用同一个缓存副本。如果服务器或应用在返回OPTIONS响应时没有正确设置Vary: Origin,就为缓存投毒创造了条件。
第四步:防护与缓解策略
防护需要从服务器/应用配置和基础设施/中间件配置两个层面入手。
-
严格的CORS策略配置(应用层):
- 避免使用通配符*: 尽量不要设置
Access-Control-Allow-Origin: *,尤其是当请求需要凭证(Cookies、Authorization头等)时。应明确指定允许的源列表,并在服务器端进行校验。 - 严格校验
Origin头: 对于反射Origin的策略,必须在服务器端有一个严格的白名单,只反射那些在白名单中的Origin值。绝对不要信任客户端传来的任何Origin值。 - 避免过度宽松的
Access-Control-Allow-Methods和Access-Control-Allow-Headers: 只列出应用实际需要的方法和头部,而不是*。
- 避免使用通配符*: 尽量不要设置
-
正确设置缓存控制头(应用层与中间件层):
- 强制
Vary: Origin头部: 确保所有CORS响应(包括对OPTIONS预检请求和实际请求的响应)都包含Vary: Origin响应头。这是防御预检请求缓存投毒的最关键措施。它明确告知缓存系统,Origin请求头的值是决定缓存版本的关键因素。 - 谨慎使用
Access-Control-Max-Age: 虽然设置较长的Access-Control-Max-Age可以提高性能,但也会延长潜在污染缓存的影响时间。在安全要求高的场景,可以适当减少此值,或仅在源(Origin)高度可信、变化不频繁的场景下使用较大值。
- 强制
-
安全的中间件/基础设施配置(运维/架构层):
- 为
OPTIONS请求配置独立的缓存策略: 在CDN、反向代理等中间件上,明确配置不对OPTIONS方法的请求进行缓存,或者为OPTIONS请求设置非常短(如0秒)的缓存时间。 - 确保缓存键包含
Origin头: 如果必须缓存OPTIONS响应,必须确保中间件的缓存键(Cache Key)将Origin请求头作为计算因子之一。这样,不同源的OPTIONS请求就不会共享同一个缓存条目。这是实现Vary: Origin语义的基础设施保障。 - 定期审计与测试: 使用自动化工具或手动测试,验证CORS配置的安全性,特别是模拟攻击者从不同源发起请求,检查响应头(
Access-Control-Allow-Origin,Vary)和缓存行为是否符合预期。
- 为
-
深度防御:
- 实施同源策略作为基础: 理解CORS是对SOP的有限、可控放宽。默认情况下,不提供跨域访问是最安全的。
- 结合其他安全机制: 即使CORS配置得当,也需要其他安全措施,如输入验证、输出编码、使用CSRF令牌等,防止因CORS配置失误或被绕过而导致严重安全问题。
总结: “CORS预检请求缓存中毒”漏洞是一个典型的安全机制交互缺陷案例。它提醒我们,单个安全机制(CORS)配置不当,与另一个性能优化机制(缓存)配置不当相结合,会产生新的攻击面。防御的关键在于理解整个请求-响应链中每个环节的行为,并通过正确设置HTTP头部(特别是Vary: Origin)和中间件策略,确保安全策略在缓存层得到正确传递和执行。