漏洞发生在登录辅助接口
SAP CAP 的多租户与可扩展性组件 @sap/cds-mtxs 负责管理租户扩展,包括调整数据模型和部署扩展数据。CVE-2026-76969 出现在它的登录辅助接口:匿名请求没有提供登录凭据时,服务端仍会使用自身的客户端凭据申请访问令牌,并将令牌返回给调用者。攻击者由此取得服务端客户端身份,进而越过租户扩展操作的权限边界。
受影响的部署需要同时满足这些条件:多租户应用使用存在漏洞的组件版本并启用可扩展性;认证使用 XSUAA,且组件能够取得其 UAA 客户端凭据;攻击者可以通过网络访问 POST /-/cds/login/token。组件可以作为独立的 mtx sidecar 辅助服务运行,也可以嵌入应用。获取令牌不需要已有账号、访问令牌、passcode 或用户交互;后续数据改写链还需要选定一个已经订阅的租户,并通过扩展激活处理 CSV 数据资源。
缺少凭据为何反而返回令牌
正常情况下,登录辅助接口应根据调用者提交的凭据申请令牌;没有任何受支持的凭据时,应直接拒绝请求。漏洞涉及两个相互衔接的问题:接口没有认证保护,且缺少凭据的请求进入了一个默认使用服务端身份的授权分支。
在 srv/extensibility-service.js 中,ExtensibilityService 的 served 回调把 POST /-/cds/login/token 直接注册到 CAP 使用的 Express Web 应用上。该路由位于 CAP 服务定义之外,因此没有受到服务上 @requires 权限声明的保护,匿名调用者也能进入处理函数。
处理函数会根据 refresh_token 选择刷新令牌授权,或根据 passcode 选择密码授权。然而,两项输入都不存在时,它没有报错,而是让授权表单保持为空。随后,处理函数把申请的权限范围(scope)设为 <xsappname>.cds.ExtensionDeveloper,并调用 authToken。这里的应用名前缀用于区分权限所属的应用,cds.ExtensionDeveloper 则对应扩展开发权限。
authToken 实际调用 lib/utils.js 中的 token()。这个通用辅助函数在未指定授权类型时,默认使用 OAuth 的 client_credentials 授权:客户端以自己的身份申请令牌,而不是代表一个已登录用户。它从服务端配置中取得 sidecar 的 clientid 和 clientsecret,通过 HTTP Basic 认证向 UAA 令牌服务证明客户端身份。于是,调用者未提供凭据,反而触发了服务端代为使用自身凭据的行为。
UAA 成功响应后,登录处理函数解析响应并把 JSON 返回给匿名调用者,其中包含 access_token。泄露的是可用于后续认证的访问令牌;这一响应并不直接暴露原始 clientsecret。问题的关键在于,调用者得到了一个代表高权限客户端身份的凭证。
内部身份如何变成租户数据改写权限
CAP 的 JWT 认证中间件负责把访问令牌中的声明转换成应用角色。JWT 是一种承载身份和权限声明的令牌格式;中间件移除 scope 中的应用名前缀后,会得到 cds.ExtensionDeveloper 角色。对于 client_credentials 令牌,如果其中的客户端 ID 与服务端配置的 clientid 相同,中间件还会授予 internal-user,即内部调用身份。泄露的令牌因此可以满足 cds.xt.ExtensibilityService 的授权要求。行为证据中,匿名访问扩展列表被拒绝,携带该令牌访问 GET /-/cds/extensibility/Extensions 则成功。
内部身份还改变了目标租户的选择规则。ExtensibilityService.set 通常使用请求上下文中的 req.tenant;当调用者拥有 internal-user 时,处理函数会接受调用者显式指定的租户。攻击者因此可以通过 POST /-/cds/extensibility/set 为选定的已订阅租户提交扩展模型、CSV 表格数据、标签和激活模式。这个分支使泄露令牌的影响不再局限于读取扩展信息,而是进入了其他租户的扩展部署流程。
后续操作存在明确的依赖关系:
set把扩展模型和 CSV 资源写入选定租户的扩展存储cds_xt_Extensions。JobsService排队执行activateExtension,由它调用DeploymentService.extend(tenant, options),开始部署该租户的扩展。- SQLite 部署插件调用
cds.deploy,根据扩展模型自动调整数据库结构,并导入 CSV 资源。
最后一步的数据写入采用 UPSERT 语义:若记录的键已存在,就更新对应记录;否则插入新记录。因此,攻击者提供的 CSV 不仅能增加数据,还能覆盖键值匹配的现有业务行。数据改写的直接原因是扩展部署流程接受了未经授权的内部身份,并把攻击者提供的资源用于指定租户。
已观察到的影响及其边界
运行行为的证据覆盖 @sap/cds-mtxs 4.0.2、@sap/cds 9.9.3 与 SQLite 部署插件。令牌签发和客户端身份到角色的映射使用了模拟身份提供方,未覆盖真实 XSUAA 的令牌签发与签名验证;存在漏洞的接口和默认授权逻辑位于未修改的目标组件中。因此,代码中的认证缺口有明确依据,但真实 XSUAA 环境下的端到端令牌行为仍存在这一验证边界。
租户数据库结构和模型的成功变更通过服务接口得到观察。业务行覆盖则通过另一个等价 SQLite 实例中的数据库检查得到确认:被测试表内的全部现有行均遭到替换。这支持“匹配的业务行可以被覆盖”的结论,不能扩展为所有表或任意数据库内容都会被整体替换。HANA 后端没有行为验证,不能直接套用 SQLite 的具体写入结果。
已演示的数据改写范围是已订阅租户。默认提供方租户的修改没有成功:空租户参数触发 HTTP 400,扩展激活也拒绝空租户标识。代码还显示,DeploymentService.unsubscribe 的 SQLite 实现能够删除租户数据库,扩展服务的 discard 或 DELETE 路径能够移除扩展;这些是潜在的破坏性后续操作,未实际执行。现有证据也未建立系统性窃取租户业务数据的能力,确认的泄露对象主要是访问令牌和作业记录,另有部分结构及模型信息可被访问。
作业列表还存在独立的信息暴露
作业服务的 Jobs 和 Tasks 数据投影在 srv/jobs-service.cds 中具有 @readonly 和 @cds.persistence.exists 声明,分别表示只读接口和使用已有持久化对象;它们都不能代替访问控制。这些投影缺少 @requires,使 GET /-/cds/jobs/Jobs 可以匿名返回作业记录。
泄露的内部令牌还可以授权 POST /-/cds/jobs/enqueue,让攻击者把可控的任务租户元数据写入作业记录,随后通过匿名作业列表读取。这里包含两个不同的权限问题:写入作业需要内部令牌,读取作业列表本身不需要认证。匿名读取属于附带的信息泄露,单凭它不能证明租户业务数据已经被修改。
修复应阻断空凭据请求的默认授权
修复的核心是在调用令牌辅助函数之前检查输入:如果 passcode 和 refresh_token 均不存在,就返回 HTTP 400,避免请求落入 sidecar 自身的 client_credentials 授权。已确认包含这一检查的版本为 3.9.7 和 4.0.3,修正后的处理函数使用 POST /-/cds/login/grant。
| 分支 | CVE 记录列出的受影响范围 | 修复证据 |
|---|---|---|
| 1.x | ≤ 1.18.3 | 1.18.4 被推定为首个修复版本;仅确认新路由注册和旧路由重定向,未确认缺少凭据时的拒绝检查及重定向状态码 |
| 2.x | ≤ 2.7.6 | 2.7.7 被推定为首个修复版本;仅确认新路由注册和旧路由重定向,未确认缺少凭据时的拒绝检查及重定向状态码 |
| 3.x | ≤ 3.9.6 | 3.9.7 已确认包含缺少两项凭据时返回 HTTP 400 的检查 |
| 4.x | ≤ 4.0.2 | 4.0.3 已确认包含上述检查,旧的 token 路由以 HTTP 308 重定向到 grant 路由 |
表中的受影响范围来自 CVE 记录,1.x、2.x 和 3.x 分支未进行行为测试。对 1.18.4 和 2.7.7 的修复判断依赖版本衔接及路由变化,不能把它们与已确认输入检查的版本视为具有相同证据强度。维护者应升级到包含该拒绝检查的版本,并以缺少两项凭据时最终返回 HTTP 400 为修复核查重点。在 4.0.3 中,旧接口返回 HTTP 308 是兼容性重定向;仅观察到重定向,并不足以确认最终处理函数会拒绝空凭据请求。
排查时,如果能够确认对 POST /-/cds/login/token 的匿名请求既不含 passcode 也不含 refresh_token,响应却成功包含 access_token,这就是该缺陷的直接可观察特征。后续还应核对非预期激活的扩展、新增的租户数据库字段,以及通过扩展 CSV 被覆盖的业务行。这些变化可以提示扩展权限遭到滥用,但需要结合扩展激活记录和获准变更判断,不能单凭一次模型变化认定攻击。
对于附带的作业信息暴露,匿名访问作业列表得到包含记录的 HTTP 200 响应,说明该读取路径缺少有效访问控制;记录中异常的租户元数据可作为进一步排查作业写入的线索。登录接口的输入检查针对的是令牌泄露,不能据此认定匿名作业读取也已修复。