## https://sploitus.com/exploit?id=889E9326-1D8F-5960-8EF2-9A4F79226A68
# CVE-2026-29000 β pac4j-jwt Library-Level PoC Lab
## TL;DR
This repository contains a **library-level PoC** for **CVE-2026-29000** in `pac4j-jwt`.
It compares vulnerable and patched behavior with two cases:
* **Baseline**: a legitimate token should be accepted
* **Attack**: a forged token should be accepted on vulnerable versions and rejected on the patched version
| Version | Baseline | Attack | Result |
|---|---:|---:|---|
| `6.0.3` | β | β | Vulnerable |
| `6.0.4.1` | β | β | Vulnerable |
| `6.3.3` | β | β | Patched |
This PoC demonstrates authenticated profile creation with attacker-controlled subject and roles on vulnerable versions, while the patched version rejects the forged token.
---
## What this project is
This is **not** a web application demo.
It is a small Java program that calls `JwtAuthenticator` directly and compares multiple versions of `pac4j-jwt` inside Docker.
The goal is to prove three things:
1. legitimate tokens still work
2. forged attacker-controlled claims are accepted by vulnerable versions
3. forged attacker-controlled claims are rejected by the patched version
---
## Why these versions were selected
The tested versions were chosen deliberately:
* **6.0.3** β included because a public technical write-up reported a working PoC on this version
* **6.0.4.1** β included because public advisory data places it inside the affected 6.x range
* **6.3.3** β included because it is the fixed release for the 6.x line
This gives the lab three useful reference points:
* a public-PoC reference version
* an advisory-confirmed affected version
* the patched version
---
## Project structure
```text
.
βββ docker-compose.yml
βββ Dockerfile
βββ pom.xml
βββ src/main/java/lab/Repro.java
```
### File roles
* `docker-compose.yml`
Defines the test matrix for each version.
* `Dockerfile`
Builds and runs the PoC inside a container.
* `pom.xml`
Defines dependencies and builds a runnable fat JAR.
* `src/main/java/lab/Repro.java`
The actual PoC harness.
---
## What the services mean
The `docker-compose.yml` file defines three services:
* `v603` = test `pac4j-jwt 6.0.3`
* `v6041` = test `pac4j-jwt 6.0.4.1`
* `patched` = test `pac4j-jwt 6.3.3`
So these commands mean βrun the PoC once against that specific versionβ:
```bash
docker compose run --rm v603
docker compose run --rm v6041
docker compose run --rm patched
```
`--rm` means the temporary container is removed after the run finishes.
---
## How to run it
### Build
```bash
docker compose build --no-cache
```
### Run
```bash
docker compose run --rm v603
docker compose run --rm v6041
docker compose run --rm patched
```
---
## What the PoC does
For each version, the program runs two cases.
### 1) Baseline
It generates a legitimate token and validates it through `JwtAuthenticator`.
Expected result:
* accepted on all tested versions
### 2) Attack
It generates a forged token with attacker-controlled claims and validates it through `JwtAuthenticator`.
Expected result:
* vulnerable versions β forged identity accepted
* patched version β forged token rejected
---
## How to read the output
### Vulnerable output
You should see something like this:
```text
[case] baseline
result: ACCEPTED
observed_subject: alice
observed_roles: [ROLE_USER]
[case] attack
result: ACCEPTED
observed_subject: admin#override
observed_roles: [ROLE_SUPERUSER, ROLE_ADMIN]
[summary]
conclusion: VULNERABLE: forged token accepted
```
Meaning:
* the normal token works
* the forged token is also accepted
* the subject and roles were replaced with attacker-controlled values
### Patched output
You should see something like this:
```text
[case] baseline
result: ACCEPTED
observed_subject: alice
observed_roles: [ROLE_USER]
[case] attack
result: REJECTED
reason: CredentialsException: A non-signed JWT cannot be accepted as signature configurations have been defined
[summary]
conclusion: PATCHED: forged token rejected
```
Meaning:
* the normal token works
* the forged token is rejected
* the patched version no longer accepts the non-signed inner JWT path used by the attack
---
## Why this repository focuses on library behavior instead of a generic token script
This CVE affects a **library-level authentication path**, not a single application with one universal role model.
The reusable part is the **attack shape**:
* forged attacker-controlled claims
* encrypted as JWE
* passed into `JwtAuthenticator`
What is **not universal** across real applications:
* claim names
* role names
* authorization mapping
* key material / JWKS setup
* application-specific profile handling
Because of that, this repository focuses on proving that the library accepts forged attacker-controlled claims on vulnerable versions, rather than pretending there is one universal token that would automatically work against arbitrary applications.
---
## Screenshots
1. `docker compose run --rm v603`
2. `docker compose run --rm v6041`
3. `docker compose run --rm patched`
### Vulnerable version: 6.0.3

### Vulnerable version: 6.0.4.1

### Patched version: 6.3.3

---
## References
* [pac4j security advisory for JwtAuthenticator](https://www.pac4j.org/blog/security-advisory-pac4j-jwt-jwtauthenticator.html)
* [GitHub Advisory Database: CVE-2026-29000](https://github.com/advisories/GHSA-pm7g-w2cf-q238)
* [NVD entry: CVE-2026-29000](https://nvd.nist.gov/vuln/detail/CVE-2026-29000)
* [CodeAnt technical write-up: public-key authentication bypass PoC](https://www.codeant.ai/security-research/pac4j-jwt-authentication-bypass-public-key)
---
## Final takeaway
This project demonstrates three core facts:
1. legitimate baseline tokens are accepted on all tested versions
2. forged attacker-controlled claims are accepted on `6.0.3` and `6.0.4.1`
3. forged attacker-controlled claims are rejected on `6.3.3`
This is the core vulnerable-versus-patched evidence for this CVE in this repository.
On vulnerable versions, the forged token is not merely parsed β it produces an authenticated profile with attacker-controlled subject and roles.