☰
CORS跨域配置错误漏洞详解:原理、验证与修复实战
2026/9/30 15:22:21 网站建设 项目流程

1. CORS跨域访问漏洞:到底是什么,能做什么

很多人在调接口时都见过这样的报错:has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource。这一行英文,前端不认识、后端不承认、运维一头雾水,最后要么是后端粗暴地加上Access-Control-Allow-Origin: *,要么是从网上复制一段中间件配置完事。但在我做Web安全测试和SRC漏洞挖掘的这几年里,最常翻车、也最容易被低估的恰恰就是CORS。很多开发人员以为CORS只是“跨域时的一个头”,配错了顶多导致页面拿不到数据,实际上CORS跨域配置错误是一种实打实的漏洞,严重情况下可以直接用受害者身份读取他个人数据、银行卡信息、通讯录甚至后台订单数据,属于典型的逻辑层配置风险,也是我在漏洞报告里经常能拿高危评级的突破口。

我写这篇文章,主要想给你讲透三件事:CORS跨域漏洞是怎么形成的,怎么在真实项目里验证它,以及一旦确认存在该怎么修。适合这几类人看:正在学SRC漏洞挖掘和渗透测试的初学者,被CORS报错折磨的前端或后端开发,以及做安全巡检或漏洞扫描的运维同学。我会把原理、利用步骤、排查过程和修复方案全部串起来,大部分内容都可以直接在你的测试环境里照着做一遍。

2. CORS工作原理与漏洞成因拆解

2.1 从同源策略开始说起

浏览器安全模型中最核心的一条规则叫同源策略。所谓“同源”,指的是协议、域名、端口三者完全一致。比如https://a.com:443/page和https://a.com:443/api是同源,但https://a.com和http://a.com不同源,https://a.com和https://b.com也不同源,https://a.com:8080和https://a.com还是不同源。同源策略限制了不同源之间的资源读取,一个页面里的JavaScript只能读取同源接口的数据,否则就会被浏览器拦截。这套机制就好比你在自己家里可以随便开抽屉,但是到了邻居家,对方不开门你就什么都拿不到。

但真实业务不可能永远同源。前端页面部署在www.example.com,后端接口却在api.example.com,或者前端服务器和后端服务器域名都不一样,这时候必须有一种机制允许特定跨域请求通过。于是W3C制定了跨域资源共享标准,也就是CORS。CORS的核心逻辑是:浏览器发现一个跨域请求后,会先把请求的Origin头告诉服务器,服务器根据这个Origin判断是否允许、允许哪个来源,再通过响应头Access-Control-Allow-Origin告诉浏览器。浏览器只认这个响应头,如果响应里没有它,或者它的值不等于请求的Origin,就会拦截响应,页面里的JS就拿不到数据。

这个流程设计得很合理,问题出在很多人配置CORS时图省事、图简单,把校验逻辑写成了“不管谁来都放行”,或者“谁带哪个Origin就放行谁”,这就等于邻居家门没锁,任何人都能进去翻东西。

2.2 三类典型的CORS配置错误

我梳理了实战中遇到最多的三种错误的CORS配置,很多网上公开的CORS跨域漏洞报告,根源都在这三类里。

第一类是直接配置Access-Control-Allow-Origin: *。这个通配符的意思是允许任意来源跨域读取。如果接口涉及用户隐私数据、个人订单、账户信息等,配合任意来源这一点,任何网站都可以在用户访问时静默发起跨域请求并读取结果。不过有一个细节:如果响应里同时带上了Access-Control-Allow-Credentials: true,浏览器会直接拒绝这种组合,因为标准规定通配符不能配合凭据使用。于是很多人在踩了这个坑之后,把通配符改成了反射Origin,这就进入了第二类。

第二类是反射任意Origin,也就是服务器不做任何校验,直接把请求头里的Origin原封不动地写进响应头Access-Control-Allow-Origin。这种配置最常见,我在不少企业内部系统、政府网站、电商后台里都见过。攻击者只需要把恶意网页的Origin设置成自己域名的值,服务器看到请求就会返回对应的Access-Control-Allow-Origin,浏览器一对比发现一致,直接放行。这类配置虽然不是最宽松的全通配,但同样等于裸露,所有来源都能跨域拿数据。更麻烦的是,这种配置往往是“每个请求都反射”,攻击者的Origin只要是动态生成的,就能稳定触发。

第三类是把Origin的校验写成了“包含匹配”或者“前缀匹配”。比如开发想允许example.com和自己公司的子域,于是写出if (origin.endswith("example.com")) return origin;这种逻辑。看起来好像有校验,实际上攻击者注册一个evilexample.com,或者搞一个example.com.evil.com,都能绕过。我在测试时经常用这种子域混淆、域名后缀伪造的方式,命中率相当高。下面这个表格可以帮你直观对比这几种配置的区别:

配置模式响应头表现能否带凭据风险等级
Access-Control-Allow-Origin: *任意Origin都能访问不能配合Credentials高,但受限
反射任意Origin请求Origin是什么就返回什么通常配合Credentials=true严重
Origin: null特殊处理直接返回Access-Control-Allow-Origin: null可以配合Credentials=true严重
后缀/前缀匹配Origin反射伪造的子域Origin通常能带凭据高危
严格白名单比对只有精确匹配才返回头可安全配合Credentials低

还有一种情况是处理Origin: null。如果页面是通过sandbox属性加载的iframe、使用file://协议或者直接打开本地HTML文件,浏览器会发送Origin: null。有些服务器遇到 “null”就直接返回Access-Control-Allow-Origin: null,结果攻击者可以构造一个带sandbox属性的iframe,直接跨域读取目标数据。这种坑很少被注意到,但一旦碰到接口在响应里返回了ACAO: null,利用成本极低。

2.3 为什么credentials=true是放大器

单独配了Access-Control-Allow-Origin: *或者反射Origin,如果接口不涉及用户登录态,危害相对有限。真正让CORS漏洞变得致命的是Access-Control-Allow-Credentials: true这个头。

这个响应头的作用是允许浏览器在跨域请求中携带Cookie、HTTP认证信息和客户端TLS证书。通俗点说,它允许跨域请求以“已登录用户”的身份访问接口。如果CORS配置根源上已经是任意Origin可反射,再叠加这个头,那么恶意网站发起的跨域请求就会自动带上受害者在目标网站的Cookie,服务器识别为合法登录用户,数据就被偷走了。这就是典型的“CORS信任链被完全攻破”场景。

我在实战测试里会把“是否有Credentials”作为判断漏洞能否写严重的分水岭。一个只反射Origin但没带Credentials的接口,只能读取不需要登录态的公开数据,危害往往在中低危;但如果带上了Credentials,能读取用户个人信息,那完全可以按高危甚至严重漏洞提交,这也是我在给漏洞定级时最看重的一点。

3. CORS跨域漏洞的检测与验证实操

3.1 手工验证:一个请求打天下

最直接的检测方法,就是给目标服务器发送一个带自定义Origin的跨域请求,看响应里是否原样返回了对应头,以及返回了哪些CORS相关响应头。我通常先用curl快速验证,开一个带Cookie的请求,行为越接近真实浏览器越好。

curl -i -H "Origin: https://evil.com" https://target.com/user/info -H "Cookie: session=xxx"

重点看响应头里有没有这几项:

  • Access-Control-Allow-Origin: https://evil.com
  • Access-Control-Allow-Credentials: true
  • Vary: Origin

如果Access-Control-Allow-Origin的值刚好等于我们发送的Origin: https://evil.com,并且Access-Control-Allow-Credentials: true,那基本可以直接断定为可稳定利用的漏洞。接下来再用浏览器实际验证一次,确认能够读到响应体。只靠curl看响应头不算最完整的证据,因为浏览器才是最终的裁判,但curl可以快速筛选出可疑目标。

有一个非常容易误判的点:如果服务器没有返回Access-Control-Allow-Origin,这不等于存在漏洞,恰恰相反,这通常是服务器的正常选择,浏览器会直接拦截跨域响应。我在一些初次接触CORS测试的同学写的漏洞报告里经常看到,他们拿“没有ACAO头”当漏洞来报,这是不对的,必须区分“服务器不主动允许”和“服务器错误允许”。前者是安全默认,后者才是漏洞。

3.2 用浏览器控制台完整复现

curl验证通过之后,我需要再证明恶意网页确实能拿到敏感数据,这就得上浏览器了。在本地起一个恶意站点,页面里写一段JavaScript,用fetch请求目标接口,然后查看控制台的网络面板和代码执行结果。

// attacker.html fetch('https://target.com/user/info', { method: 'GET', credentials: 'include' }) .then(res => res.text()) .then(data => { document.getElementById('out').textContent = data; }) .catch(err => { document.getElementById('out').textContent = 'Fetch Error: ' + err.message; });

浏览器默认在跨域请求中不携带Cookie,但加上credentials: 'include'后,浏览器就会带上Cookie。如果目标响应里同时存在Access-Control-Allow-Origin: http://evil.com和Access-Control-Allow-Credentials: true,浏览器就会把响应数据交给页面上的JS,攻击者就能直接读取到data的内容,把它回传到自己的服务器。实际操作中,我在验证时还会在恶意页面里加一个img或者XMLHttpRequest回传逻辑,这样能演示完整的数据窃取链。

需要注意的是,fetch在跨域场景下还有一个预检流程。如果请求方法是GET、POST且Content-Type为text/plain或application/x-www-form-urlencoded等简单请求,浏览器不会发OPTIONS预检;但如果带着自定义请求头、使用application/json或使用PUT/DELETE等方法,浏览器会先发一个OPTIONS预检请求。在测试时,我需要同时验证预检和非预检两种情况,因为有些配置错误只影响其中一种流程。

3.3 常见测试技巧与工具辅助

对于目标范围较大的测试,纯手工点肯定效率太低,我会用Burp Suite配合插件批量检测。思路是在抓包工具里修改请求包,批量添加各种测试用的Origin值,观察响应头的变化。下面这些测试值是我每测都会过一遍的基础集:

  • https://evil.com
  • https://target.com.evil.com
  • http://target.com
  • null
  • https://evil.target.com(如果你不确定目标的子域名解析规则,就测一个看起来像子域但实际不存在的)
  • https://target.com@evil.com

另外,Burp Suite社区的CORS插件、J2EEScan、CSP-Finder等,都可以辅助收集带CORS响应头的接口,加快审计节奏。但工具只能帮你找候选,最终确认漏洞还是得靠手工验证和浏览器证据,这个步骤不能省。

还有一个偏门但好用的技巧:去目标网站的JS文件里搜fetch(、XMLHttpRequest、axios,把前端实际请求的接口列表整理出来,再对这些接口批量做CORS测试。很多时候开发配置CORS是有选择性的,只有少数接口带了跨域头,直接扫描发现不了,但前端代码里能找出所有接口,逐个试一遍就能扩大战果。

3.4 漏洞能否被利用的三个决定性条件

这里必须多说一句:CORS跨域漏洞能不能实际造成危害,取决于三个条件的同时满足。

第一个条件是接口返回的数据必须包含敏感信息。即使CORS配置完全失控,但接口只返回公开的天气信息或版本号,那只是低危甚至信息泄露都算不上。第二个条件是请求必须能带上受害者身份,也就是Cookie或认证信息。我测试时会把Cookie带上看是否生效,如果接口本身不用Cookie,而用自定义Token或者本地存储,那么单纯CORS配置错误加上credentials: include也拿不到用户的登录态,这种情况下危害等级会大打折扣。第三个条件是浏览器环境下能够自由发起跨域请求并读取响应,也就是前面说的,响应头必须允许对应Origin,并且不能触发浏览器拦截。

我在写漏洞报告时,会把这三点逐项列出来说明,评审平台的高质量报告也都是这么分析的。你可以把这三个条件记成本能反应,做CORS测试时先在脑子里过一遍,少走很多弯路。

4. 真实业务场景中的影响评估与利用思路

4.1 攻击者通过CORS能窃取到什么

在业务系统完整、用户数据齐全的站点里,CORS配置错误可以导致攻击者以受害者身份读取任意有跨域配置的接口数据。举几个真实场景:

电商站点的用户接口/user/order/list返回订单列表,包含收件人姓名、手机号、收货地址。如果接口允许任意Origin且带credentials,攻击者只需要诱导受害者打开恶意网页,页面里的JavaScript就会自动带上受害者的登录Cookie去请求订单接口,再把返回的JSON数据回传给攻击者服务器。我测试过一些站点,通过这种手段拿到的数据细节甚至比CSRF更完整,因为CSRF只能发请求但读不到响应,而CORS能让攻击者直接看到服务器返回的所有内容。

企业内部系统的用户资料接口、财务系统的交易记录接口、SaaS平台的API Key管理接口,都是CORS漏洞的高价值目标。遇到了能读取到这些数据的CORS配置问题,直接往严重漏洞方向写。有些场景里CORS还能配合子域名接管、XSS漏洞做组合利用,比如某个主域名信任了某子域名,而那个子域名本身可以被攻击者控制,那么攻击者就能以被信任子域名为跳板,绕过主域名的CORS校验读取主域名数据。

4.2 CORS与CSRF、XSS的联动利用

说一个实战价值很高的组合拳思路:CORS漏洞的核心能力是“跨域读取响应”,CSRF的核心能力是“跨域发送请求”,XSS的核心能力是“在受害者页面执行任意脚本”。这三者单独拎出来都有一定限制,但组合起来经常能把一条很弱的线索放大成严重问题。

举个例子:某个接口https://api.example.com/v1/users配置了反射Origin并且带credentials。攻击者可以做如下攻击链:在恶意页面上用js向https://api.example.com/v1/users发起跨域请求,拿到受害者个人信息后,再把数据POST到攻击者的服务器。整条链路里没有任何步骤需要执行受害者站点的脚本,完全靠CORS配置漏洞打通了跨域数据读取。如果再把目标换成能修改数据的接口,比如修改邮箱、重置密码之类的,攻击者就能直接操作受害者账户。

另外,有些业务在内网部署了运维后台,虽然外部不可达,但如果后台的某个HTML页面存在XSS,攻击者可以利用XSS在浏览器上下文中发起同源请求。此时即使后台没有配置CORS,XSS脚本也能读取响应。这种情况下CORS不再必要,因为XSS已经在同源环境里执行了。所以我的经验是:CORS漏洞经常是锦上添花型放大,而XSS是釜底抽薪型直取。在评估一个系统的综合风险时,应该把CORS配置纳入整体攻击面来考虑,不要仅孤立地看单个漏洞。

4.3 如何评估CORS漏洞的严重等级

根据我在SRC平台提交和评审漏洞的经验,CORS漏洞评级主要看下面几个因素:

因素低危倾向严重倾向
请求是否携带凭据不携带携带Cookie/Token
返回数据敏感度公开信息个人敏感数据、订单、后台配置
影响范围单接口全站或全API网关接口
是否需要额外条件需要用户点击链接访问即触发
可读取性响应无法直接读取响应可直接用JS读取

如果只想快速给一个参考值:仅反射Origin但没有Access-Control-Allow-Credentials: true,一般中低危;反射Origin且带Credentials,能读取敏感数据,高危起步;如果接口还能写操作、影响大量用户,直接报严重。这里也要说句大实话,真实测试时不是所有“CORS配置错误”都值得花时间深入,有些接口数据本来就是公开的,配置再宽松也只是低危,把有限的时间花在高价值接口上,产出比高得多。

5. 漏洞修复与加固实践:从配置根源解决问题

5.1 最根本的修复思路:精确白名单

CORS跨域漏洞的核心问题不是“不能有跨域”,而是“不该允许的来源被允许了”。所以修复方案的第一性原理就是:只允许明确已知的合法来源,绝不反射任意Origin,也绝不用通配符配合凭据。

正确的做法是,在后端维护一个来源白名单,把请求里的Origin与白名单逐一精确匹配,只有完全一致才在响应里返回对应的Access-Control-Allow-Origin,否则不返回这个响应头。同时要设置Vary: Origin,避免CDN或者浏览器缓存把某个Origin对应的响应头错误地返回给另一个Origin。

白名单匹配时要注意几个容易踩的坑。不能用字符串包含去判断,example.com会被evilexample.com绕过;不能用正则里的.当通配符,需要转义为\.;要区分协议,https://api.example.com与http://api.example.com来源不同,如果只需要HTTPS就把HTTP排除掉。我见过一个项目使用PHP代码写校验时,直接用strpos($origin, 'example.com') !== false就判断为允许,这种写法等于把整个域名体系都暴露给了任何包含example.com的恶意域名,比如example.com.attack.com,非常危险。下面这段伪代码展示了规范的白名单匹配逻辑:

// 伪代码:白名单精确匹配 $allowList = ['https://www.example.com', 'https://admin.example.com']; $origin = $_SERVER['HTTP_ORIGIN'] ?? ''; if (in_array($origin, $allowList, true)) { header('Access-Control-Allow-Origin: ' . $origin); header('Vary: Origin'); } // 不在白名单里则什么都不返回

以FastAPI为例,正确配置CORS中间件的代码如下,注意我没有使用allow_origins=["*"],而是精确列出了允许的来源:

from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app = FastAPI() origins = [ "https://www.example.com", "https://admin.example.com", ] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE"], allow_headers=["Content-Type", "Authorization"], )

在Flask里可以用Flask-CORS库,也可以在视图函数里手动设置响应头。手动设置的好处是可控性更强,尤其适合接口数量不多的小项目。Node.js的Express里,我一般用cors中间件,传入一个函数动态返回允许来源,这和FastAPI的配置思路一致。Spring Boot的@CrossOrigin注解和CorsConfiguration类也同理,不要用allowedOrigins("*")加allowCredentials(true)的组合,这是Spring中明确禁止的,启动时会直接报错。

5.2 必须避开的几个错误配置组合

修复CORS时,我整理了一个高频错误清单,任何一个都能让修复形同虚设:

第一个是Access-Control-Allow-Origin: *加Access-Control-Allow-Credentials: true。标准上都明确不允许这种组合,浏览器看到带credentials的通配符会直接拒绝响应。但有些旧框架或网关会自动补上credentials,导致配置失效,修复时要把这两个头都检查一遍。

第二个是动态生成Access-Control-Allow-Origin时,把非法来源也放进了白名单。有些人的代码逻辑是先判断再拼接,但判断写错了。比如允许来源列表来自配置,但配置里写了一个废弃的旧子域,这个子域如果早就被释放或被攻击者接管,攻击者就可以通过劫持一个被信任的子域来绕过CORS防护。所以在维护白名单时,要定期清理不再使用的子域名或内部域名。

第三个是信任了不该信任的第三方域名。有些业务为了对接外部合作伙伴而临时加了某个域名的跨域权限,后来合作结束但配置一直没删。我在测试的时候就专门找这种历史遗留的第三方域名,它的安全水位往往远低于主站,一旦它先被攻破,主站的CORS防护也就跟着被绕过。

第四个是使用Access-Control-Allow-Origin: null来兼容本地开发。这个我在前面提过,Origin: null可以被任何sandbox iframe伪造,宁可让本地开发走代理,也绝不能在生产环境允许null来源。修复时不仅要把响应头配置改掉,还要检查有没有反向代理或WAF规则对第三方的数据报文做修改,把非法请求的Origin头抹掉。有些代理为了方便认为会把Origin: null或非允许来源的请求转发给上游时不带Origin头,这会让后端一层防护直接失效。这里需要特别确认的是,检查网络链路层行为,一般只需要关注Web服务器和接入层的配置,不要把范围扩大到系统网络本身。如果测试环境有相关设备,可以重点核查接入层是否对请求头做了重写规则。

5.3 全链路配置建议:Nginx、CDN与后端一起改

我修复过很多CORS漏洞,最大的教训是:只改应用后端远远不够,全链路都得检查。

Nginx做反向代理时,如果应用本身已经正确返回CORS响应头,但Nginx配置里又叠加了一层add_header Access-Control-Allow-Origin http://www.example.com;,那么所有经过Nginx的请求都会收到这个头,后端白名单就形同虚设。更麻烦的是,Nginxadd_header的生效范围和location层级有关,很容易出现“有的接口加了有的接口没加”的混乱状态。正确做法是:全链路只在一个层级设置CORS响应头,其余层级透传,不要层层设置。

另外,如果站点用了CDN,CDN通常会对Access-Control-Allow-Origin、Vary等响应头做缓存。如果CDN缓存了某个Origin对应的CORS响应,再返回给别的Origin,就会产生安全和功能双重问题。这时候一定要在CDN层把Vary: Origin纳入缓存Key计算,或者干脆把CORS响应头放到CDN源站控制,让CDN跟随源站。

还有一点容易忽略:如果业务接口跨域请求需要携带Cookie,那么Access-Control-Allow-Origin的值绝不能是*,必须精确返回白名单里的那个Origin值,并且同时返回Access-Control-Allow-Credentials: true。这两个头缺一不可。很多人修CORS时只加了ACAO,发现请求还是带不上Cookie,然后又在响应头里补上credentials,结果发现浏览器仍然拦截,原因往往是ACAO返回的是*而不是具体Origin,这组组合必须成对出现才行。

5.4 配置安全帽:上线的验证与巡检

修完配置之后,至少要重复一遍第一节里的所有验证步骤,确认以下内容:

  • 请求Origin: https://evil.com时,响应里没有Access-Control-Allow-Origin: https://evil.com
  • 请求Origin: https://www.example.com(合法的)时,响应里有对应的ACAO且值为该合法来源
  • 请求Origin: null时,响应里没有Access-Control-Allow-Origin: null
  • 请求合法Origin且返回credentials时,页面JS能正常读取数据
  • 响应头里有Vary: Origin

我自己的习惯是,把上述验证脚本写成一段自检测试,修复后跑一遍,上线后再定期跑一遍。对于体量较大的站点,可以用Python写一个简单的巡检脚本,定时检查所有重要接口的CORS响应头是否符合白名单策略。下面是一个示例:

import requests ORIGIN_TEST = "https://evil.com" TARGETS = [ "https://www.example.com/api/user/info", "https://www.example.com/api/order/list", ] ALLOWED_ORIGIN = "https://www.example.com" session = requests.Session() for url in TARGETS: r = session.get(url, headers={"Origin": ORIGIN_TEST}) acao = r.headers.get("Access-Control-Allow-Origin", "") if acao == ORIGIN_TEST: print(f"[VULN] {url} reflects arbitrary Origin: {acao}") else: print(f"[OK] {url} does not reflect arbitrary Origin")

6. 常见问题与排查技巧实录

6.1 明明改了配置,浏览器还是报CORS错

很多次修完CORS后,开发反馈“配置肯定改了,但前端还是报错”。这时候不要急着怀疑代码,优先检查浏览器缓存。浏览器对CORS响应是有缓存的,特别是带Access-Control-Max-Age头的预检响应,缓存时间甚至可能是10分钟或更久。我在测试时经常遇到这个坑:改了服务端配置,但前面已经有一个旧响应被缓存了,浏览器直接用缓存里的旧策略拦截,看起来就像没改一样。

处理方式很简单:在浏览器无痕窗口里测试,或者清掉这个域名的缓存。使用DevTools的Network面板时,勾选“Disable cache”也能有效避免缓存干扰。如果你是用fetch测试,还可以在请求里加一个随机参数破坏缓存命中的URL匹配,效果也一样。

如果是后端开发在排查线上问题,还得多检查一层:看发出去的请求是否命中CDN缓存节点。早先在部分CDN边缘节点上,对OPTIONS预检请求的处理是有缺陷的,会导致预检响应头丢失。遇到这种情况,需要刷新CDN缓存或者调整CDN上针对OPTIONS请求的缓存策略,而不是反复改后端代码。

6.2 加了多个自定义请求头,预检直接凉了

前端调用接口时如果带了自定义头,比如Authorization、X-Requested-With或者X-CSRF-Token,浏览器会先发OPTIONS预检请求,预检响应需要通过Access-Control-Allow-Headers明确允许这些头,后续的真实请求才会发出去。我见过好多次,CORS配置里只写了Access-Control-Allow-Origin,完全没管Headers,导致预检阶段就挂了,前端一直在报错。

排查时,先用curl模拟OPTIONS请求,重点看响应头里的Access-Control-Allow-Headers和Access-Control-Allow-Methods:

curl -i -X OPTIONS https://target.com/api/user/info \ -H "Origin: https://www.example.com" \ -H "Access-Control-Request-Method: GET" \ -H "Access-Control-Request-Headers: Authorization"

然后对比前端实际携带的自定义头,是否都被允许。如果漏了,在后端CORS配置里把需要的头加进allow_headers列表。另外提醒一下,写的时候不要图省事把Access-Control-Allow-Headers设成*然后还开allow_credentials=true,这也是标准上不允许的组合,浏览器会拒绝。正确做法是列出项目实际用到的所有自定义头。

6.3 测试结论容易忽略的一个细节:请求的“简单性”

我在讲CORS利用时,一直强调“简单请求”和“预检请求”的区别。测试时如果只测了带application/json和自定义头的请求,可能忽略了最简单的GET请求;反之如果只测简单请求,也可能忽略了预检请求下的配置差异。稳妥的做法是两类请求都覆盖一遍,记录下两类请求各自的响应头。

有一个真实发生过的事情,某系统的CORS配置在预检请求上出现了问题,开发顺手在Nginx层把所有OPTIONS请求的响应都统一加上了Access-Control-Allow-Origin: *和Access-Control-Allow-Headers: *,好让预检通过。我当时测的时候发了一个简单的GET请求(不带自定义头),根本不触发预检流程,但响应里因为Nginx全局配置带了Access-Control-Allow-Origin: *,配合接口自身的Access-Control-Allow-Credentials: true,直接在简单请求上读到了数据。这种配置不一致造成的漏洞漏洞很隐蔽,靠常规预检测试根本发现不了,必须同时测两类请求才看得清楚。

6.4 我踩过的一个真实坑:子域信任链被反向利用

最后分享一个我记忆深刻的案例。做某个SRC项目时,主站example.com的CORS白名单包含tools.example.com和dev.example.com两个子域名。我用常规反射Origin的手法试了下主站,没有任何CORS头返回,看起来防御做得挺规范。但我深入测了一下这两个被信任的子域,发现dev.example.com上挂着的是一个很老的管理后台,存在存储型XSS漏洞。我把XSS脚本写入后台的某个输入框,当管理员打开后台页面时,脚本在主站同域策略的信任关系下执行,向主站接口发起了跨域请求。因为主站信任dev.example.com,CORS响应头正常返回,XSS脚本顺利读取了接口数据。

这件事让我彻底明白:CORS白名单里每一个被信任的来源,都在无形中扩展了攻击面。修复CORS漏洞时不光要看配置本身,还要看看名单里的域名是否安全、是否存在被接管的风险。如果一个子域已经废弃但没有从白名单里删除,甚至域名解析都已经被释放,攻击者可以直接注册这个域名,让整个CORS白名单保护形同虚设。在做CORS加固时,我会顺手把所有白名单域名的解析记录、备案状态、历史漏洞都过一遍,把不安全的子域剔除出去。

7. 写在最后的一点个人经验

CORS跨域访问漏洞说到底是“信任边界”弹得太大导致的问题。它不像SQL注入那样能直接拖库,也不像RCE那样能直接控制服务器,但它给攻击者提供了一条非常顺滑的“跨域读数据”通道,在真实业务中的影响不容小觑。我自己做漏洞挖掘时,会把CORS列入登录接口、用户中心、订单接口等核心页面必测的清单里,因为这类接口但凡配错,往往能直接拿到高价值数据。

如果你是在做SRC漏洞挖掘,我给你的建议是:测出CORS漏洞后千万别只写“存在CORS配置错误”就完事,要按前面提到的三个决定条件,把能读取的接口、返回的数据样例、受害者的触发方式都写成证据链。哪怕只是一个看起来普通的反射Origin,只要能证明可以读回含有手机号或订单的数据,评审都会给不错的评级。我见过不少同学因为在报告里只贴了响应头截图,导致原本高危的问题被降级,非常可惜。

如果你是在做开发或运维,我给你的建议是:CORS配置不要自己手搓正则,不要图快用通配符,老老实实维护白名单列表;每次上线前把上面那套自检测试脚本跑一遍,顺手检查一下响应头里的Vary: Origin。很多安全问题就出在贪图省事的五分钟里,而修复它可能只需要认真读完这篇文章的时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询