CVE-2026-76187:Airflow Keycloak provider 跨客户端认证绕过

客户端凭据跨越了应用边界

Apache Airflow 的 Keycloak provider 在客户端凭据认证流程中缺少客户端范围检查:服务端接受同一 Keycloak 认证域内其他应用的有效凭据,并为其服务账号签发 Airflow 会话。CVE-2026-76187 的核心问题是,Keycloak 对某个客户端的认证成功,被直接当成了为它建立 Airflow 身份的依据。

Airflow 3 可以通过 KeycloakAuthManager 将认证交给 Keycloak。Keycloak 的 realm 是组织用户、客户端和认证配置的逻辑域,一个 realm 可以容纳多个应用。客户端代表接入 Keycloak 的应用;其中,机密客户端可以使用客户端标识 client_id 和客户端密钥 client_secret 证明自己的身份。启用服务账号后,客户端还能通过 OAuth 的 client_credentials 授权流程,以应用自身的身份获取令牌,无需用户登录。

共享 realm 使多个应用使用同一认证域,但其他应用的凭据有效,并不意味着它有资格获得 Airflow 会话。这个流程需要同时确认凭据有效,以及提交凭据的客户端属于 Airflow 允许的范围。受影响实现只完成了前一项。

服务端如何签发跨客户端会话

认证管理器挂载在 /auth,其令牌路由对外形成无需预先登录的 POST /auth/token 接口。处理函数根据请求中的 grant_type 选择认证流程;当值为 client_credentials 时,客户端凭据处理逻辑直接使用请求体中的 client_id 和 client_secret。这两个字段都由调用者控制。

随后,create_client_credentials_token 将这组凭据交给 KeycloakAuthManager.get_keycloak_client,并通过 client.token(grant_type="client_credentials") 向配置的 realm 请求令牌。服务端在此之前既没有比较提交的客户端标识与配置项 [keycloak_auth_manager] client_id,也没有应用客户端允许列表。因此,Keycloak 收到的可以是同一 realm 中另一应用的凭据,并按该应用的配置正常完成认证。

Keycloak 接受凭据后,Airflow 使用返回信息中的 sub 字段作为用户标识,构造 KeycloakAuthManagerUser。其中,sub 表示认证主体;显示名称则取自 preferred_username、clientId 或服务账号回退名称。随后,generate_jwt 为这个身份生成 Airflow 会话令牌,并以 HTTP 201 在响应的 access_token 字段中返回。

JWT 是带签名的身份声明令牌。这里的令牌由 Airflow 自己签发,攻击者无需伪造签名:漏洞发生在签发之前的身份接纳决策。原本只证明“另一应用的客户端凭据有效”的结果,就这样变成了 Airflow 认可的认证身份。

令牌签发与会话生效还存在一层依赖。在所述 Airflow 3.3+ 会话路径中,KeycloakJWTMiddleware 通过 Cookie 处理认证,涉及 _token、_access_token 和 _refresh_token。其中,缺少 _access_token 时,get_user_from_token 不会返回用户,而服务账号流程没有刷新令牌。因此,持有签发出的 JWT 并不单独保证会话可用,还必须满足中间件的 Cookie 要求。

哪些部署具备利用条件

利用条件包括:Airflow 3 使用 KeycloakAuthManager,安装的受影响 provider 已实现 client_credentials 流程;配置的 realm 还包含其他启用了服务账号的机密客户端;攻击者持有其中一个客户端的有效标识和密钥。攻击者不需要 Airflow 凭据,也不需要用户交互。

网络可达性同样有明确边界:攻击者需要访问 POST /auth/token,Airflow 则需要访问其配置的 Keycloak 服务。由于凭据交换由 Airflow 代为完成,攻击者无需直接访问 Keycloak。

公布的受影响版本范围为 apache-airflow-providers-keycloak >= 0, < 0.10.0,其中 0.9.0 为最后一个受影响版本。依据功能实现提交和发布时间推定的可利用区间是 0.5.0–0.9.0,但其下界未经逐版本源码检查确认,不能将它视为已经确定的受影响范围。具体行为观察覆盖 provider 0.9.0、Airflow 3.3.1 和 Keycloak 26.3.5 的组合,未覆盖公布范围内的每个版本。

已确认的影响与授权边界

在上述版本组合中,跨客户端签发的令牌满足会话要求后,GET /ui/auth/me 接受了另一应用的服务账号身份并返回 HTTP 200;无凭据请求则返回 HTTP 401。这说明攻击者能够跨越客户端边界,成为 Airflow 已认证的主体,因而可以访问仅要求认证的端点。

资源级操作仍取决于该服务账号已有的权限。受保护的 /api/v2/* 端点还涉及 Keycloak UMA 权限评估,即根据资源和请求主体判断是否允许访问的授权机制。此次认证绕过本身不会赋予管理员权限。

现有观察没有建立成功执行资源级操作的证据:受保护的 API 请求因 realm 缺少 UMA 授权资源而返回 HTTP 500。这是与配置有关的结果,不能据此认定所有部署都会阻止后续资源访问。无需认证的 /api/v2/monitor/health 和 /api/v2/version 无论是否携带会话都返回 HTTP 200,也不能用来证明绕过成功。

此外,该接口还可被用作同一 realm 内其他客户端的凭据验证入口。受影响实现会把调用者提交的凭据转发给 Keycloak,并通过成功时的 HTTP 201 与凭据被拒绝时的 HTTP 403 区分结果,使调用者能够判断某组客户端凭据是否被接受。

修复与排查重点

应将 apache-airflow-providers-keycloak 升级至修复版本 0.10.0。补丁源码及上游公告支持的修复行为是:服务端在请求 Keycloak 之前,先要求提交的 client_id 与 [keycloak_auth_manager] client_id 完全一致;不匹配时直接返回 HTTP 403。该版本的修复行为尚无运行时测试结果支持。

拒绝响应沿用凭据交换失败时的 Client credentials authentication failed,减少通过不同错误响应枚举客户端标识的机会。检查发生在访问 Keycloak 之前,因此也阻止了该接口代为验证无关客户端的密钥。修复针对的是客户端范围判断这一缺失环节。

排查时,可关联 POST /auth/token 返回 HTTP 201、随后 GET /ui/auth/me 返回 HTTP 200 的请求,并核对签发 JWT 中的名称及认证身份响应是否指向配置的 Airflow 客户端之外的服务账号。跨客户端身份与会话被接受同时出现,才是更有意义的异常线索。

HTTP 201 本身不能区分利用与正常签发,因为配置的 Airflow 客户端使用有效凭据也会得到相同状态码;HTTP 403 也可能只是密钥无效。判断应结合身份归属和会话使用结果,不能仅依赖响应状态码。

参考资料