
CVE-2026-29000 - pac4j-jwt (< 4.5.9 / < 5.7.9 / < 6.3.3) JwtAuthenticator authentication bypass PoC
JwtAuthenticator Authentication Bypass PoCA proof-of-concept for CVE-2026-29000, an authentication bypass in the
pac4j-jwt JwtAuthenticator component
(CVSS 10.0, Critical).
The script forges a token that impersonates any user with any role — without knowing a single secret — as long as the target exposes its RSA public key.
[!WARNING] This tool is provided for educational purposes and authorized security testing only. It was written to solve the Hack The Box machine "Principal", and the defaults (
iss,role,kid) are tailored to that box. Only use it against systems you own or are explicitly authorized to test.
pac4j-jwt versions < 4.5.9, < 5.7.9, and < 6.3.3 contain a logic
flaw in JwtAuthenticator when it processes encrypted JWTs (JWE).
When JwtAuthenticator decrypts a JWE, it attempts to parse the inner token as
a SignedJWT. If the inner token is an unsigned PlainJWT (alg: none),
the SignedJWT object comes back null and, due to the logic error, the
signature-verification path is skipped entirely. The authenticator then
builds a user profile straight from the unverified claims.
In other words, the outer JWE encryption — which only requires the server's public key — is mistaken for proof of authenticity, so any attacker who can read the public key can mint fully trusted tokens.
/api/auth/jwks).PlainJWT (alg: none) with attacker-chosen claims
(sub, role, iss, …).PlainJWT in a JWE envelope encrypted to the target's public key.Bearer token. The vulnerable
JwtAuthenticator decrypts it, skips signature verification, and trusts the
forged claims.requirements.txt:pip install -r requirements.txt
(requests, jwcrypto, cryptography)
python3 exploit.py -u <username> [options]
| Flag | Description | Default |
|---|---|---|
-t, --target | Base URL of the target (e.g. http://host:8080) | — |
--jwks-url | JWKS endpoint path | /api/auth/jwks |
--jwks-file | Load JWKS from a local file instead of fetching from --target | — |
-u, --username | Value for the sub claim (required) | — |
--role | Value for the role claim | ROLE_ADMIN |
--iss | Value for the iss claim | principal-platform |
--exp | Token validity in seconds | 3600 |
--verify-endpoint | Protected endpoint to test the forged token against (e.g. /api/settings) | — |
-k, --insecure | Disable TLS certificate verification | off |
-o, --output | Write the forged JWE to a file instead of stdout | stdout |
-v, --verbose | Print the forged JWT payload to stderr | off |
You must provide either --target (to fetch the JWKS over the network) or
--jwks-file (to supply it locally).
Forge an admin token by fetching the public key from the target and print it to stdout:
python3 exploit.py -t http://target:8080 -u admin
Forge a token, then automatically test it against a protected endpoint:
python3 exploit.py -t http://target:8080 -u admin \
--verify-endpoint /api/settings -v
Use a locally saved JWKS and save the token to a file:
python3 exploit.py --jwks-file jwks.json -u admin -o token.jwe
Upgrade org.pac4j:pac4j-jwt to a patched release:
Released under the MIT License.