pac4j-jwt 身份验证绕过漏洞CVE-2026-29000
pac4j-jwt 是 pac4j 安全框架的核心模块,专门用于基于 JWT(JSON Web Token)的身份认证与授权,广泛用于Java应用的身份验证。
一、基本情况
pac4j-jwt 是用于 pac4j 框架的 JWT(JSON Web Token)令牌处理扩展组件,提供 JWT 的生成、解析、验证、签名/验签等全套能力。

pac4j-jwt 模块可以无缝集成到 Java/Java EE 应用(比如 Spring Boot、JEE、Play Framework)中,从而可以实现无状态的身份认证。
栋科技漏洞库关注到 pac4j-jwt 在 4.5.9、5.7.9 和 6.3.3 之前的版本中的漏洞,漏洞现已被追踪为CVE-2026-29000,CVSS 4.0评分10。
二、漏洞分析
CVE-2026-29000漏洞是在4.5.9、5.7.9 和 6.3.3之前的版本中,其 JwtAuthenticator 类在处理加密 JWT(JWE)时存在高危安全漏洞。
该漏洞由 CodeAnt AI 安全研究团队发现,存在于库中 JwtAuthenticator 组件对加密令牌(JWE)的处理逻辑中,该漏洞潜在风险极高。
正常情况下,标准的 JWT 安全机制会依赖签名(JWS)验证令牌完整性,而许多企业系统同时也会使用加密(JWE)来隐藏令牌内容。
该漏洞位于两种机制结合处, JWE 封装明文 JWT 攻击:攻击者构造无签名 PlainJWT,并用服务器公钥将其封装进 JWE 加密容器中。
由于组件在解密后未能正确验证内层 JWT 的签名,攻击者可伪造包含任意 Subject(如 admin)和 Role 的身份令牌从而绕过身份验证。
服务器解密令牌后,库内的 toSignedJWT() 函数会因内部令牌未签名而正确返回 null。
但后续的签名校验模块仅通过简单的 null 判断就被直接跳过。
库会直接使用令牌中未经验证的声明信息创建用户档案,相当于将伪造的 PlainJWT 视为可信身份。
攻击者只需要在令牌中随意指定 subject(用户)和 role(角色)字段,无需知晓服务器私钥,即可轻松冒充任意用户包括系统管理员。
攻击者如果获取了服务器 RSA 公钥,可以构造一个嵌套了未签名 PlainJWT 的恶意 JWE 令牌获取管理员后台与敏感数据的全部权限。
三、POC概念验证
1、获取服务器的RSA公钥
在RSA-JWE部署中,服务器的RSA公钥用于加密令牌。根据定义,公钥是公共的。它们通常可通过以下方式获得:
JWKS(JSON Web密钥集)端点——许多框架在/.well-known/jwks.jso中公开了这一点
·应用程序的文档或配置文件
·TLS证书检查
·签入密钥的公共代码存储库
如果您的部署使用基于RSA的JWE,攻击者几乎肯定已经拥有您的公钥。
很好,RSA加密就是这样设计的。公钥可以让你加密。只有私钥可以解密。
问题是pac4j使用相同的公钥进行JWE解密,而身份验证旁路只需要加密端。
2、精心制作恶意声明
攻击者创建了JWT声称他们想要的任何东西:任何主题、任何角色、任何权限。
JWTClaimsSet maliciousClaims = new JWTClaimsSet.Builder()
.subject("admin")
.claim("$int_roles", List.of("ROLE_ADMIN", "ROLE_SUPERUSER"))
.claim("email", "attacker@evil.com")
.expirationTime(new Date(System.currentTimeMillis() + 3_600_000))
.build();
3、创建无签名明文 JWT
这是关键步骤,攻击者创建的不是签名的JWT(JWS),而是PlainJWT。根据规范,这是一个完全有效的JWT——它只是没有签名。
PlainJWT innerJwt = new PlainJWT(maliciousClaims);
JWT规范定义了三种类型的令牌——JWS(签名)、JWE(加密)和PlainJWT(未签名)。
PlainJWT是一个真实的东西,库支持它,当Nimbus的toSignedJWT()遇到一个库时,它会返回null——因为它没有签名。
4、使用公钥封装JWE
攻击者将未签名的PlainJWT封装在JWE中,并用服务器的RSA公钥加密:
JWEObject jweObject = new JWEObject(
new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
.contentType("JWT")
.build(),
new Payload(innerJwt.serialize())
);
jweObject.encrypt(new RSAEncrypter(publicKey));
String maliciousToken = jweObject.serialize();
从外部看,这个令牌看起来像任何其他JWE令牌。它被正确加密了。
它将在服务器上成功解密。在解密之前,服务器无法将其与合法令牌区分开来,到那时,漏洞已经触发。
5、提交并验证为任何人
攻击者将令牌作为Bearer令牌发送:
管理员已设置登录后刷新可查看在服务器上:
JWE解密成功(令牌已使用正确的公钥加密)✅
toSignedJWT()返回null(内部有效载荷是PlainJWT,而不是JWS)
如果(signedJWT!=null)为false,则完全跳过签名验证
使用攻击者的任意声明调用createJwtProfile()
攻击者通过ROLE_admin和ROLE_SUPERUSER身份验证为管理员
无需私钥、无需共享密钥、无需暴力破解,只需使用本来就设计为公钥的那个公钥即可。
四、以下是完整的、有效的PoC,这是针对真正的pac4j jwt 6.0.3配置运行的:
1、POC代码
管理员已设置登录后刷新可查看2、结果输出
[ATTACKER] Token crafted using only public key
[ATTACKER] eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwiY3R5Ijoi...
[BYPASS] Authenticated as: admin#override
[BYPASS] Roles: [ROLE_ADMIN, ROLE_SUPERUSER]
已通过管理员身份验证,具有超级用户权限、只使用公钥。
五、影响范围
pac4j-jwt < 4.5.9
pac4j-jwt < 5.7.9
pac4j-jwt < 6.3.3
六、修复建议
pac4j-jwt >= 4.5.9
pac4j-jwt >= 5.7.9
pac4j-jwt >= 6.3.3
七、参考链接
管理员已设置登录后刷新可查看