前言
在多租户微服务架构落地MCP(Model Control Plane)服务时,绝大多数企业的鉴权校验都存在一个致命盲区:只校验JWT签名、过期时间、签发主体,完全忽略受众(aud)字段校验。这不是小众的代码bug,是大量自研MCP、开源二次改造MCP项目普遍存在的共性架构问题。
很多开发和运维下意识认为,只要Token签名合法、没过期、由可信IdP签发,请求就具备合法性。这套粗放的校验逻辑放在早期单体架构、独立租户部署的场景里,几乎不会暴露问题,日常迭代和测试都能平稳运行。但在云原生普及后,多微服务、多租户共用一套IdP签发体系成为行业常态,这套残缺的鉴权逻辑会直接击穿租户数据隔离壁垒,形成可以被批量扫描、批量利用的高危越权漏洞。
我在多次线上渗透测试和企业安全整改中见过大量真实案例:攻击者仅需注册一个平台普通租户账号,拿到属于自己的合法Token,不需要破解密钥、不需要篡改签名、不需要利用复杂漏洞,就能直接遍历读取平台内其他所有租户的模型配置、任务日志、权限清单和业务数据。整个攻击过程全程正常鉴权通过,没有任何报错、没有过期拦截,常规安全审计工具和日志监控完全无法识别异常行为。
更关键的是,这类漏洞潜伏期极长,大多是上线半年甚至数年才偶然被发现,期间所有租户的数据都处于裸奔状态。本文从底层JWT设计第一性原理出发,抛开网上碎片化的表层讲解,完整拆解MCP Server无Aud校验的漏洞本质、完整攻击链路、架构缺陷根源,提供可直接本地复现的漏洞代码、标准化攻击测试流程、三大主流编程语言的生产级修复方案、自动化漏洞检测脚本,同时梳理企业落地MCP多租户鉴权的标准化落地规范,帮研发和安全团队彻底规避这类高危业务安全漏洞。
一、底层原理:为什么Aud字段是MCP服务的鉴权边界底线
想要彻底读懂这个漏洞,不能只停留在“代码少写了一个字段校验”的表层认知。所有安全漏洞的根治,都要回归技术设计的第一性原理。我们需要从JWT原生设计逻辑、微服务信任模型、多租户隔离机制三个维度,搞懂Aud字段不可替代的安全价值。
1.1 JWT各字段的核心权责划分
JWT的每一个标准声明都有固定的安全职责,不存在冗余设计。业界九成以上的鉴权漏洞,本质都是研发混淆了不同字段的校验定位,用单一校验逻辑覆盖了多维度的安全防护需求。
iss(签发者):证明Token的来源可信,确保Token来自企业内部授权的IdP服务,而非外部伪造的第三方服务。它只解决信任来源的问题,保证Token不是外人随便生成的。
exp(过期时间):限制Token的有效生命周期,防止长期有效Token泄露后被永久利用。它解决的是时效安全问题,规避静态Token长期滞留带来的风险。
sub(主体):标识Token归属的用户、机器账号或客户端,用于精准识别请求的发起主体。它解决的是身份归属问题,用来区分具体是谁在发起请求。
aud(受众):整个JWT体系中,唯一用来标识「当前Token允许访问的目标服务」的字段。它解决的是服务权限边界问题,是微服务架构下不可或缺的隔离屏障。
很多团队的核心误区,也是所有同类漏洞的根源:用签名校验替代受众校验。JWT签名的作用极其单一,仅仅是保证Token内容未被篡改、签发主体真实有效。签名只能证明Token“真不真”,完全无法判定Token“合不合适”,不会、也不可能校验这个Token的使用场景和目标服务。
用最通俗的实景类比:签名证明这是一张官方正规的通行证,Aud证明这张通行证仅限进入某一个特定房间。只验签名不验Aud,等同于拿着商场、酒店、写字楼的正规通行证,就能随意闯入机房、保密室等核心涉密场景,所有服务边界直接形同虚设。
1.2 多租户MCP架构的IdP签发逻辑
目前国内企业主流的MCP部署架构,几乎全部采用统一IdP授权体系。一套中心化授权服务,统一为前端控制台、后端业务服务、MCP管控服务、模型推理服务、运维后台、监控服务等所有内部组件签发Token。
为了降低运维成本,多数企业不会拆分密钥体系,所有服务的Token会共用同一套RSA公私钥对完成签名校验。这就导致一个致命现状:任意服务、任意租户的Token,签名都能被MCP Server成功校验,不存在签名非法的情况。
不同服务的Token,在签名、签发者、过期规则上完全一致,唯一的区分特征就是aud字段。前端应用Token的aud固定为web-console,运维后台Token的aud为admin-server,业务服务Token的aud为business-service,只有MCP专属Token的aud为mcp-control-plane。
一旦MCP Server主动关闭或遗漏Aud校验逻辑,所有非MCP服务的合法Token,都会变成访问MCP服务的有效凭证。攻击者不需要伪造任何数据,只需要拿到平台任意合法Token,就能完成跨服务、跨租户的越权访问。
1.3 漏洞成立的三个必要前置条件
这个漏洞不会在所有架构中触发,必须同时满足三个前置条件,这也是企业自查风险、快速定位隐患的核心依据。只要命中任意两个条件,就需要立刻开展安全整改。
第一,系统采用软隔离多租户架构,核心数据隔离完全依靠Token内的tenant_id字段实现,数据库、接口层没有物理隔离或二次校验兜底。这是目前SaaS类MCP平台的主流设计。
第二,全服务共用一套IdP签名密钥,不同业务线、不同微服务、不同租户的Token签名算法和密钥完全一致,没有做服务级密钥拆分。
第三,MCP Server鉴权逻辑存在缺失,仅常规校验iss签发者、exp过期时间、RSA签名合法性,完全缺失aud受众的强制校验。
二、架构拆解:漏洞环境完整架构与数据流
为了让大家跳出纯代码层面的认知,直观理解完整攻击链路,我梳理了标准漏洞环境的完整架构,覆盖普通租户用户、攻击者、统一IdP授权中心、多微服务集群、MCP Server、业务数据库的全链路交互逻辑。
A[攻击者-租户A用户] -->|1.登录获取合法Token| B[统一IdP授权中心]
C[正常用户-租户B用户] -->|2.登录获取租户B Token| B
B -->|3.签发带tenant_id、aud字段JWT| D[前端Web服务]
B -->|4.签发各服务通用Token| E[后端业务服务]
B -->|5.签发MCP专属Token| F[MCP Server]
D -->|6.携带非MCP aud Token请求MCP| F
E -->|7.跨服务Token横向请求MCP| F
F -->|8.仅校验签名/exp/iss,无aud校验| G[鉴权通过]
G -->|9.读取Token内tenant_id| H[业务数据库]
H -->|10.返回对应租户全量数据| A
```
从架构图可以清晰看到,整个攻击链路没有任何高危操作,所有请求都是合法合规的正常请求,唯一的缺陷就是MCP服务的鉴权断点。在安全架构下,步骤6、7的非MCP专属Token应该被直接拦截拒绝,而漏洞环境中会全部放行,直接打通越权通道。
2.1 漏洞数据流详细拆解
1. 统一IdP根据客户端的访问场景、客户端标识,针对性签发不同aud值的JWT Token,所有Token签名合法、字段完整、有效期正常,不存在任何伪造特征;
2. 攻击者通过自有普通租户账号,正常登录平台获取前端控制台Token,该Token的aud字段匹配前端服务,和MCP服务无任何关联,但携带合法的租户A标识;
3. 攻击者通过简单的接口遍历、替换租户ID参数,或利用平台可能存在的Token泄露漏洞获取其他租户凭证,构造访问MCP核心接口的请求;
4. MCP Server解码Token后,依次校验签名、过期时间、签发主体,全部校验通过,因无aud校验直接放行请求;
5. 服务直接提取Token内的租户标识,带入SQL查询语句,匹配对应租户的MCP模型配置、权限列表、训练任务数据、接口调用日志等核心数据;
6. 业务数据库无二次租户校验、无行级权限隔离,直接返回目标租户的全量业务数据,一次完整的跨租户越权攻击就此完成。
三、漏洞复现:源码级还原风险场景
我将用Python还原企业线上高频出现的漏洞代码,完全复刻无Aud校验的MCP鉴权逻辑。这段代码是很多团队二次开发MCP、自研轻量化MCP服务的通用写法,大家可以直接复制到本地运行复现,快速验证自身业务环境是否存在同款高危漏洞。
3.1 漏洞版MCP Token鉴权代码
该代码仅做基础的Token合法性校验,完全缺失核心的aud受众校验,是生产环境中最常见的漏洞形态,隐蔽性极强,常规代码审查很难发现问题。
importjwtfromdatetimeimportdatetime# 模拟企业统一IdP公钥、签发地址配置IDP_PUBLIC_KEY="""-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuF9Z7x9X7Z8xZ8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8 -----END PUBLIC KEY-----"""EXPECTED_ISS="https://idp.company.com"defmcp_token_verify(token:str)->dict:"""漏洞版Token校验函数:无aud受众校验"""try:# 仅校验签名、签发者、过期时间,缺失aud校验payload=jwt.decode(token,IDP_PUBLIC_KEY,algorithms=["RS256"],issuer=EXPECTED_ISS,options={"verify_aud":False}# 关键漏洞:关闭aud校验)return{"status":True,"data":payload,"msg":"鉴权成功"}exceptjwt.ExpiredSignatureError:return{"status":False,"data":None,"msg":"Token已过期"}exceptjwt.InvalidIssuerError:return{"status":False,"data":None,"msg":"签发主体非法"}exceptExceptionase:return{"status":False,"data":None,"msg":f"鉴权失败:{str(e)}"}# 模拟攻击测试if__name__=="__main__":# 模拟前端Web服务Token(aud非MCP服务,属于非法受众)attack_token="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."res=mcp_token_verify(attack_token)print(res)# 输出:鉴权成功,直接放行非法受众Token3.2 攻击测试流程
1. 正常登录平台前端控制台,抓取自身租户的前端Token,通过jwt.io解析可以清晰看到,该Token的aud字段为web-console,并非MCP服务专属标识;
2. 直接携带该前端Token,请求MCP服务的租户配置查询、模型列表、任务日志、权限配置等核心敏感接口;
3. 服务正常返回200状态码和完整业务数据,鉴权完全通过,无任何拦截;
4. 在IdP未强绑定租户字段的场景下,手动修改Token内tenant_id字段,批量遍历平台所有租户ID,即可批量获取全平台所有租户的核心数据。
3.3 漏洞核心危害总结
第一,微服务横向渗透风险。所有接入统一IdP的微服务Token都可以互通冒用,MCP服务的专属访问边界彻底失效,攻击者可以从前端服务横向渗透至核心管控服务。
第二,全量跨租户数据泄露。平台任意低权限租户账号,都可以访问、查询、读取高权限租户的模型配置、业务数据、权限策略、运维记录,租户隔离体系完全崩塌。
第三,攻击隐蔽性极强。整个攻击过程没有签名错误、没有权限报错、没有异常日志,常规安全设备和人工审计完全无法识别,漏洞可以长期潜伏在生产环境。
四、根源对抗分析:为什么常规安全策略拦不住该漏洞
很多企业安全团队都会产生疑惑:我们已经做了签名校验、过期校验、租户隔离、接口权限控制,为什么还是会出现跨租户入侵问题?核心原因是传统安全策略存在天然逻辑盲区,只能对抗伪造、过期、非法来源的Token,完全无法对抗“合法但越场景”的Token滥用问题。
4.1 签名校验的能力边界
RSA签名校验只负责两件事:Token数据的真实性和完整性。它能保证Token没有被第三方篡改、是官方IdP真实签发,但完全无法判定这个Token的使用场景是否合法、是否适配当前服务。
这和现实中的证件逻辑完全一致:公安局颁发的身份证绝对真实有效,但真实的身份证不代表可以随意进入涉密场所。身份真实和场景合法是两套完全独立的安全维度,绝大多数团队混淆了两者的边界,最终导致鉴权体系残缺失效。
4.2 多租户隔离的设计缺陷
多数自研MCP服务的租户隔离逻辑极度依赖Token字段,属于典型的软隔离设计。研发团队直接从Token解析tenant_id,拼接SQL查询数据,没有任何兜底防护机制。
这种设计在正常访问场景下完全没问题,但一旦出现Token冒用、跨服务复用的情况,漏洞会直接爆发。没有服务级租户白名单、没有数据库行级隔离、没有接口二次鉴权,所有隔离逻辑完全依附于Token,极其脆弱。
4.3 安全测试的固有盲区
常规渗透测试和代码审计,重点检测的是无Token访问、伪造Token、过期Token、篡改Token的非法场景,几乎不会测试「合法Token跨服务复用」的场景。这就导致该漏洞长期存在于各类上线项目中,成为无人发现的安全死角。
五、全语言修复方案:落地级鉴权改造
针对该漏洞,最核心、最有效的修复方式就是开启Aud受众强制校验,拒绝所有非MCP专属的Token请求。同时搭配多层兜底策略,杜绝各类越权风险。下面提供Python、Java、Go三种主流MCP开发语言的完整生产级修复代码,可直接替换线上漏洞代码,无需大幅改造业务逻辑。
5.1 Python 安全鉴权代码(生产可用)
importjwtfromdatetimeimportdatetime# 生产环境固定配置:MCP服务唯一受众标识,全局统一MCP_EXPECTED_AUD="mcp-control-plane-v1"IDP_PUBLIC_KEY="""-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuF9Z7x9X7Z8xZ8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8 -----END PUBLIC KEY-----"""EXPECTED_ISS="https://idp.company.com"defmcp_safe_token_verify(token:str)->dict:"""安全版Token校验:强制aud受众校验,拦截跨服务Token复用"""try:# 启用JWT原生aud强制校验,精准拦截非MCP受众Tokenpayload=jwt.decode(token,IDP_PUBLIC_KEY,algorithms=["RS256"],issuer=EXPECTED_ISS,audience=MCP_EXPECTED_AUD,options={"verify_aud":True})# 业务兜底校验:确保租户字段合法有效ifnotpayload.get("tenant_id")orlen(payload.get("tenant_id"))<5:return{"status":False,"data":None,"msg":"租户标识不合法"}return{"status":True,"data":payload,"msg":"鉴权成功"}exceptjwt.ExpiredSignatureError:return{"status":False,"data":None,"msg":"Token已过期"}exceptjwt.InvalidIssuerError:return{"status":False,"data":None,"msg":"签发主体非法"}exceptjwt.InvalidAudienceError:return{"status":False,"data":None,"msg":"Token受众不匹配,禁止跨服务访问"}exceptExceptionase:return{"status":False,"data":None,"msg":f"鉴权失败:{str(e)}"}5.2 Java 安全鉴权代码(SpringBoot MCP)
importio.jsonwebtoken.*;importorg.springframework.stereotype.Component;@ComponentpublicclassMcpJwtValidator{// MCP服务固定唯一受众标识privatestaticfinalStringEXPECTED_AUD="mcp-control-plane-v1";privatestaticfinalStringEXPECTED_ISS="https://idp.company.com";// 注入IdP公钥privatefinalStringpublicKey="-----BEGIN PUBLIC KEY-----\nxxx\n-----END PUBLIC KEY-----";publicClaimsvalidateToken(Stringtoken){try{returnJwts.parserBuilder().setPublicKey(Keys.hmacShaKeyFor(publicKey.getBytes())).requireIssuer(EXPECTED_ISS).requireAudience(EXPECTED_AUD)// 强制受众校验,拦截非法场景Token.build().parseClaimsJws(token).getBody();}catch(InvalidAudienceExceptione){thrownewSecurityException("Token受众不匹配,拒绝跨服务越权访问");}catch(Exceptione){thrownewSecurityException("Token鉴权失败,请求非法");}}}5.3 Go 安全鉴权代码(Golang MCP)
packagemainimport("errors""github.com/golang-jwt/jwt/v4")// 固定MCP服务受众与IdP签发地址const(expectedAud="mcp-control-plane-v1"expectedIss="https://idp.company.com")funcMcpTokenVerify(tokenStrstring)(jwt.MapClaims,error){token,err:=jwt.Parse(tokenStr,func(token*jwt.Token)(interface{},error){// 校验签名算法,防止算法篡改攻击if_,ok:=token.Method.(*jwt.SigningMethodRSA);!ok{returnnil,errors.New("非法签名算法")}// 解析公钥returnjwt.ParseRSAPublicKeyFromPEM([]byte("-----BEGIN PUBLIC KEY-----\nxxx\n-----END PUBLIC KEY-----"))})iferr!=nil{returnnil,err}claims,ok:=token.Claims.(jwt.MapClaims)if!ok||!token.Valid{returnnil,errors.New("无效Token")}// 双层强制校验:签发者+受众ifclaims["iss"]!=expectedIss{returnnil,errors.New("签发主体非法")}ifclaims["aud"]!=expectedAud{returnnil,errors.New("Token受众不匹配,禁止跨服务访问")}returnclaims,nil}六、自动化检测脚本:批量扫描环境漏洞
为了方便大家快速排查测试环境、预发环境、生产环境的MCP服务漏洞,我编写了可直接运行的自动化检测脚本。脚本无需复杂配置,一键运行即可自动识别服务是否存在无Aud校验的跨租户越权漏洞,适配所有基于JWT鉴权的MCP服务。
importrequestsimportjson# 需自行替换为本地/线上MCP真实接口地址MCP_API_URL="http://localhost:8080/mcp/api/tenant/list"# 需自行替换为任意前端/非MCP服务的合法TokenTEST_WEB_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."defcheck_mcp_aud_vuln():"""自动化检测MCP服务Aud校验缺失高危漏洞"""headers={"Authorization":f"Bearer{TEST_WEB_TOKEN}","Content-Type":"application/json"}try:res=requests.get(MCP_API_URL,headers=headers,timeout=5)ifres.status_code==200:print("【高危漏洞确认】MCP服务未开启Aud受众校验,存在严重跨租户越权风险")print("接口响应数据:",json.dumps(res.json(),ensure_ascii=False,indent=2))else:print("【环境安全】MCP服务已开启Aud校验,非法跨服务Token已成功拦截")exceptExceptionase:print("检测异常,请检查接口地址与网络连通性:",str(e))if__name__=="__main__":check_mcp_aud_vuln()脚本使用逻辑非常简单:填入目标MCP查询接口地址和任意一个前端、运维后台等非MCP场景的合法Token,请求成功返回200数据即代表存在漏洞,返回401/403拦截则代表环境安全,校验逻辑正常生效。
七、多层架构加固:彻底杜绝跨租户越权
仅修复Aud校验只能解决当前已知漏洞,无法抵御后续衍生的各类越权风险。想要实现长期安全防护,需要搭建多层立体防护体系,从IdP签发源头、服务鉴权中层、数据存储底层、日志审计监控四个维度全面加固,彻底封死跨租户、跨服务越权链路。
7.1 IdP层源头加固
1. 实现Token字段强绑定,将aud受众、tenant_id租户标识、用户UID三者固化绑定,IdP签发时写入固定字段,禁止篡改、覆盖、替换,从源头杜绝租户ID篡改越权;
2. 拆分服务级密钥体系,为MCP、前端、业务服务、运维后台分配独立ClientID和独立签名密钥,彻底解决全服务共用密钥导致的Token复用问题。
7.2 服务鉴权层加固
1. 所有MCP接口统一开启aud、azp双重校验,仅放行MCP专属受众、专属客户端的Token,拦截所有跨服务Token请求;
2. 全局关闭JWT解码的verify_aud=False参数,删除所有跳过受众校验的代码分支,杜绝人为绕过、配置疏漏导致的漏洞复发;
3. 区分用户Token和机器账号Token的使用场景,细化接口权限粒度,核心管控接口仅放行机器专属Token,规避用户账号横向渗透风险。
7.3 数据层兜底加固
1. 所有MCP业务数据表统一保留tenant_id字段,所有查询、更新、删除SQL强制拼接租户条件,不依赖Token字段做唯一校验,实现数据层兜底拦截;
2. 开启数据库行级权限隔离,不同租户的数据做逻辑隔离,高安全场景可实现物理分表、分库存储,彻底杜绝跨租户数据泄露。
7.4 审计监控层加固
1. 单独采集、标记aud不匹配的异常请求日志,接入监控告警系统,出现跨服务访问行为即刻触发告警;
2. 监控租户ID批量遍历、高频查询陌生租户数据的行为,通过频率限制和行为识别,自动拦截暴力枚举租户数据的攻击链路。
八、运维落地规范:MCP多租户鉴权配置清单
我整理了生产环境落地的标准化配置清单,研发、测试、运维可逐项核对验收,避免上线遗漏安全配置,从流程上规避漏洞复发。
1. 已定义MCP服务全局唯一aud标识,环境迭代、版本更新不随意修改;
2. JWT解码逻辑强制开启aud校验,代码中无任何关闭、跳过受众校验的分支;
3. 各微服务已拆分独立签名密钥,无全局共用密钥、跨服务密钥复用场景;
4. IdP签发Token时,实现tenant_id、aud、用户身份三方强绑定,字段不可篡改;
5. 所有MCP业务接口具备数据层租户过滤兜底逻辑,不依赖单点Token鉴权;
6. 异常aud访问、跨服务请求已接入日志审计和实时告警,形成闭环监控。
九、面试核心考点梳理
该漏洞是当前云原生、MCP微服务、多租户业务安全的高频面试考点,聚焦架构认知和实战落地,核心问答整理如下。
Q:JWT签名合法为什么还会出现跨服务越权漏洞?
A:JWT签名只校验Token的真实性和完整性,不校验场景合法性。缺少aud受众校验,会导致其他服务的合法Token可以直接冒用访问MCP服务,击穿微服务的访问边界。
Q:多租户架构下Aud校验的核心作用是什么?
A:精准区分Token的目标访问服务,实现微服务之间的安全隔离,从根源防止跨服务Token复用、跨租户数据越权的风险。
Q:如何彻底根治MCP跨租户Token越权问题?
A:核心是开启强制aud受众校验,配合IdP密钥拆分、Token字段强绑定、数据层租户隔离、异常日志审计多层加固,形成全链路防护体系。
结语
MCP Server无Aud校验的跨租户越权漏洞,不属于高危利用的0day漏洞,却是典型的认知不足导致的架构级安全缺陷。多数团队过度迷信JWT签名的防护能力,把“身份真实”等同于“权限合法”,忽略了微服务场景下的边界隔离需求,最终让整套多租户隔离体系彻底失效。
所有微服务、多租户架构的鉴权设计,都必须坚守核心原则:身份可信、时效合法、场景适配三者缺一不可。单纯依赖签名校验的粗放式鉴权逻辑,完全无法适配复杂的云原生架构,只有做好多层校验、多层加固,才能从根源规避业务越权风险。
互动提问
1. 你们公司的MCP/微服务鉴权逻辑中,是否开启了Aud受众强制校验?
2. 你在落地多租户架构时,遇到过哪些Token复用导致的越权问题?欢迎评论区交流。