← all walkthroughs

Fortune

Other· Insane· Credential Access· Privilege Escalation
owned
2026-07-12
time to own
20m24s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

The attacker found an OpenBSD web server hosting a fortune-cookie CGI application that passed the user-supplied database name directly to the OS shell without any validation, allowing unauthenticated command execution as the _fortune web service account.

Because outbound connections from the target were blocked, every subsequent file read was performed in-band by injecting commands that printed file contents between marker strings and extracting the block from the HTTP response.

Using that channel, the attacker read the intermediate Certificate Authority private key from a directory accessible to the web process, then forged a client certificate trusted by the server's HTTPS endpoint and obtained an SSH private key for the nfsuser account.

Logging in as nfsuser triggered OpenBSD's authpf, which dynamically opened firewall rules and exposed the previously hidden NFS /home share.

That share used AUTH_SYS authentication, blindly trusting whatever UID the client declared; the attacker mounted it as UID 1000 (charlie's server UID), planted an SSH key, and obtained an interactive charlie shell.

From charlie's session, the pgAdmin4 SQLite credential database was copied and the stored PostgreSQL DBA password reversed using pgAdmin4's known-weak key derivation scheme.

That same password had been set as the root account password, completing full system compromise.

Attack path — how the box was taken

Mapped exposed services and identified the fortune CGI application, then Confirmed unauthenticated OS command injection via the /select CGI parameter, then Exfiltrated the intermediate CA private key and certificate chain via in-band injection, then Forged a trusted mTLS client certificate and retrieved the authpf SSH identity from /generate, then Triggered authpf to open PF firewall rules and expose the NFS share, then Spoofed charlie's UID over NFS to plant an SSH key and obtain a user shell, then Decrypted the DBA password from pgAdmin4's SQLite credential store, then Used the recovered DBA credential as the root password to gain full system control.

1ReconService and version enumeration (T1046)
Mapped exposed services and identified the fortune CGI application
A full TCP port scan of <retired-instance-ip> revealed OpenBSD httpd on ports 80 (HTTP) and 443 (HTTPS), SSH on port 22, and NFS-related services on ports 111 (rpcbind) and 2049 (nfs). Browsing port 80 surfaced a 'Your Fortune' web application with a /select endpoint that accepted a POST parameter named 'db'. The HTTPS site on port 443 required a client certificate, limiting unauthenticated access until a trusted certificate could be obtained.
Exact commands 2
Full TCP port scan with service/version detection; saves results to fortune_full.txt.
nmap -sV -sC -p- --min-rate 2000 -oN fortune_full.txt $TARGET
Inspect HTTP headers and home page to identify the CGI application structure.
curl -sS -i http://$TARGET/ | head -40
2ExploitationOS Command Injection (CWE-78 / T1059.004)
Confirmed unauthenticated OS command injection via the /select CGI parameter
The fortune application's /select endpoint passed the 'db' POST parameter directly into a shell command without sanitisation. Appending a semicolon and an OS command (e.g., ;id) caused the server to execute it and return output in the HTTP response body, confirming code execution as the _fortune service account. Because outbound TCP connections from the target were blocked, all subsequent file exfiltration used an in-band marker pattern — injecting 'echo BEGIN; cat FILE; echo END' and isolating the result from the HTTP response with sed.
HTTP response to db=fortunes;id contained uid= identifying the _fortune service account, with command output reflected inline in the response body.
Exact commands 2
Probe the db parameter; %3B is a URL-encoded semicolon. uid= in the response confirms injection.
curl -i -sS --max-time 10 -X POST -d 'db=fortunes%3Bid' http://$TARGET/select
Test all three candidate injection endpoints; confirms /select db= is the live sink.
for spec in "http://$TARGET/select|db" "http://$TARGET/cgi-bin/fortune|f" "http://$TARGET/cgi-bin/fortune|file"; do u=${spec%|*}; p=${spec#*|}; echo "===== $u $p ====="; curl -ksS --max-time 8 -i -X POST --data-urlencode "$p=;id" "$u"; echo; done
3Credential AccessPrivate key theft via OS command injection (T1552.004)
Exfiltrated the intermediate CA private key and certificate chain via in-band injection
With code execution confirmed, the attacker enumerated /home/bob/ca/ and found a Fortune Intermediate CA (bob@fortune.htb, signed by Fortune Root CA) whose private key was stored with permissions readable by the _fortune web service account. Each file was exfiltrated in-band: injecting 'echo BEGINKEY; cat <file>; echo ENDKEY' into the db parameter and extracting the marked block from the HTTP response with sed. The intermediate private key, its certificate, and the CA chain were all recovered, granting the ability to issue new certificates the server would trust.
In-band extraction of /home/bob/ca/intermediate/private/intermediate.key.pem via the /select injection; openssl rsa -check confirmed a valid RSA private key.
Exact commands 1
Verify the recovered key is valid and matches the certificate subject and issuer.
openssl rsa -in int.key -check -noout && openssl x509 -in int.crt -noout -subject -issuer
4ExploitationCertificate forgery using stolen CA material (T1553.004)
Forged a trusted mTLS client certificate and retrieved the authpf SSH identity from /generate
Using the stolen intermediate CA key, the attacker generated a new RSA key pair, created a certificate signing request, and signed it with the intermediate CA — producing a certificate the server treated as fully trusted. Presenting this certificate to the HTTPS /generate endpoint returned a PEM-encoded RSA SSH private key for the nfsuser account. Reading sshd_config via the same injection confirmed that nfsuser's shell is /usr/sbin/authpf: any authenticated SSH login as that account dynamically modifies PF firewall rules for the source IP.
curl with the forged client certificate against https://<retired-instance-ip>/generate returned a full RSA PRIVATE KEY PEM block; sshd_config Match User nfsuser confirmed the authpf shell assignment.
Exact commands 2
Generate an attacker key pair and certificate signing request.
openssl genrsa -out attacker.key 2048 && openssl req -new -key attacker.key -out attacker.csr -subj '/CN=attacker/O=Fortune CA'
Sign the CSR with the stolen intermediate CA key to produce a server-trusted client certificate.
openssl x509 -req -in attacker.csr -CA int.crt -CAkey int.key -CAcreateserial -out attacker.crt -days 365 -sha256
5Lateral MovementOpenBSD authpf per-user firewall bypass (T1562.004)
Triggered authpf to open PF firewall rules and expose the NFS share
Authenticating as nfsuser with the retrieved SSH key executed the authpf shell, which dynamically injected PF rules permitting the attacker's source IP to reach services previously blocked at the firewall. A post-authentication scan with rpcinfo and showmount confirmed that portmapper, mountd, and the NFS daemon were now reachable and that /home was exported. The authpf session was kept open in the background to maintain the firewall rules throughout the attack.
showmount -e <retired-instance-ip> returned '/home *' only after the nfsuser authpf session was established; the same scan returned nothing before authentication.
Exact commands 2
Open the authpf session in the background; must remain running to keep the PF rules active.
ssh -i nfsuser.key -o IdentitiesOnly=yes -o StrictHostKeyChecking=no nfsuser@$TARGET &
Confirm portmapper responds and mountd/nfsd are registered post-authpf.
rpcinfo -p $TARGET
6Lateral MovementNFS AUTH_SYS UID spoofing for unauthorized file write (T1210)
Spoofed charlie's UID over NFS to plant an SSH key and obtain a user shell
NFSv3 with AUTH_SYS authentication trusts the numeric UID provided by the client machine without any cryptographic verification. The attacker created a local OS account with UID 1000 — matching charlie's server UID — mounted /home from the NFS export, and used that UID-trusted access to write an attacker-controlled SSH public key into charlie's .ssh/authorized_keys through the mount point. A subsequent SSH login as charlie with the matching private key succeeded, providing an interactive shell.
Mount of <retired-instance-ip>:/home with local UID 1000 allowed direct write to charlie/.ssh/authorized_keys; ssh -i /tmp/charlie_id charlie@<retired-instance-ip> returned uid=1000(charlie).
Exact commands 2
Mount the NFS export; vers=3 and nolock avoid NFSv4 Kerberos requirements.
sudo mount -t nfs -o vers=3,nolock $TARGET:/home /mnt/fortune
Create a local account with UID 1000 matching charlie's server UID, then generate a key pair as that UID.
sudo useradd -u 1000 -M fakecharlie 2>/dev/null; sudo -u fakecharlie ssh-keygen -t rsa -f /tmp/charlie_id -N '' -q
7Credential AccessRecoverable credential extraction from pgAdmin4 SQLite database (CWE-257 / T1555)
Decrypted the DBA password from pgAdmin4's SQLite credential store
Enumeration from charlie's session revealed pgAdmin4 running on localhost:8081 (confirmed via /etc/httpd.conf), backed by a SQLite database at /var/appsrv/pgadmin4/pgadmin4.db. pgAdmin4 stores server passwords encrypted with AES-CFB8 using a key derived from a predictable application constant with no per-installation randomness — a scheme documented publicly and reversible offline by anyone with access to the database file. The attacker copied the file via scp and ran a decrypt script adapted from public pgAdmin4 research, recovering the plaintext password for the 'dba' PostgreSQL server entry from the joined server/user table rows and the embedded AES IV.
pgadmin4.db server JOIN user table yielded IV [REDACTED: sensitive value]; AES-CFB8 decryption using the pgAdmin4 key derivation algorithm returned the plaintext DBA credential [REDACTED: recovered credential]
Exact commands 2
Copy the pgAdmin4 SQLite credential store to the attacker machine.
scp -i /tmp/charlie_id charlie@$TARGET:/var/appsrv/pgadmin4/pgadmin4.db /tmp/pgadmin4.db
Inspect the raw encrypted credential, embedded IV, and user record from the joined tables.
sqlite3 /tmp/pgadmin4.db "SELECT s.name, s.password, u.email, u.password FROM server s JOIN user u ON s.user_id = u.id;"
8Privilege EscalationCredential reuse across database service account and OS root (T1078)
Used the recovered DBA credential as the root password to gain full system control
The plaintext password recovered from pgAdmin4 was also configured as the root account password on the operating system. From charlie's interactive SSH session, running 'su - root' with the recovered credential immediately produced a root shell with no further exploitation required. Both flags were read from disk, confirming complete system compromise.
su - root from charlie's session with [REDACTED: recovered credential] returned uid=0(root); /root/root.txt read as <root.txt>.

Attack patterns used

The transferable techniques behind the compromise.

OS Command InjectionExploitationT1059.004

What it is

The fortune application's /select endpoint passed the 'db' POST parameter directly into a shell command without sanitisation. Appending a semicolon and an OS command (e.g., ;id) caused the server to execute it and return output in the HTTP response body, confirming code execution as the _fortune service account. Because outbound TCP connections from the target were blocked, all subsequent file exfiltration used an in-band marker pattern — injecting 'echo BEGIN; cat FILE; echo END' and isolating the result from the HTTP response with sed.

Why it works

Replace the shell-out with a safe parameterised alternative: maintain a server-side allowlist of valid fortune-file names and select from that list rather than passing raw user input to a shell. If a shell invocation is unavoidable, validate 'db' strictly against the allowlist before use and reject any non-matching value with an HTTP 400. Additionally, run the CGI process as a dedicated low-privilege account (e.g., _fortune) with read access limited only to the fortune files directory — never /home or /etc — and apply OpenBSD pledge(2)/unveil(2) or equivalent OS-level confinement to restrict what the process can access even if a future bug is discovered.

Private key theft via OS command injectionCredential AccessT1552.004

What it is

With code execution confirmed, the attacker enumerated /home/bob/ca/ and found a Fortune Intermediate CA (bob@fortune.htb, signed by Fortune Root CA) whose private key was stored with permissions readable by the _fortune web service account. Each file was exfiltrated in-band: injecting 'echo BEGINKEY; cat <file>; echo ENDKEY' into the db parameter and extracting the marked block from the HTTP response with sed. The intermediate private key, its certificate, and the CA chain were all recovered, granting the ability to issue new certificates the server would trust.

Why it works

Move the CA private key off the web server entirely. Store it on a dedicated offline or air-gapped signing host, or in a hardware security module (HSM). If the key must remain on the server for operational reasons, restrict ownership to root (mode 0400) and ensure it is never accessible to any web-facing or service account. Immediately revoke all certificates issued by this intermediate CA, generate a new intermediate CA key pair from a secure environment, and reissue all legitimate client certificates. Review and harden the /generate endpoint to ensure it cannot be misused to issue shell identities to forged certificate holders.

NFS AUTH_SYS UID spoofing for unauthorized file writeLateral MovementT1210

What it is

NFSv3 with AUTH_SYS authentication trusts the numeric UID provided by the client machine without any cryptographic verification. The attacker created a local OS account with UID 1000 — matching charlie's server UID — mounted /home from the NFS export, and used that UID-trusted access to write an attacker-controlled SSH public key into charlie's .ssh/authorized_keys through the mount point. A subsequent SSH login as charlie with the matching private key succeeded, providing an interactive shell.

Why it works

Migrate the NFS export to Kerberos authentication (sec=krb5p) so that user identity is cryptographically proven rather than client-asserted. In /etc/exports, replace the wildcard (*) with an explicit list of trusted management host IPs. Enable root_squash and all_squash to remap all client-supplied UIDs to an unprivileged anonymous account. For user home directories specifically, evaluate whether NFS export is operationally necessary; if not, remove the export entirely. As a defence-in-depth measure, firewall NFS ports (111, 2049) to only those specific management IPs even when authpf rules are active.

Recoverable credential extraction from pgAdmin4 SQLite databaseCredential AccessT1555

What it is

Enumeration from charlie's session revealed pgAdmin4 running on localhost:8081 (confirmed via /etc/httpd.conf), backed by a SQLite database at /var/appsrv/pgadmin4/pgadmin4.db. pgAdmin4 stores server passwords encrypted with AES-CFB8 using a key derived from a predictable application constant with no per-installation randomness — a scheme documented publicly and reversible offline by anyone with access to the database file. The attacker copied the file via scp and ran a decrypt script adapted from public pgAdmin4 research, recovering the plaintext password for the 'dba' PostgreSQL server entry from the joined server/user table rows and the embedded AES IV.

Why it works

Remove all saved server passwords from pgAdmin4. Supply database credentials at connection time using PostgreSQL pgpass files (owned by the connecting OS user, mode 0600, never shared) or a dedicated secrets manager such as HashiCorp Vault. Restrict /var/appsrv/pgadmin4/ to the pgAdmin4 service account only (mode 0700) so that no other OS user can read the database file. Upgrade pgAdmin4 to the latest release, which includes improved credential handling. Immediately rotate the exposed DBA password and audit all systems where that credential was in use.

Credential reuse across database service account and OS rootPrivilege EscalationT1078

What it is

The plaintext password recovered from pgAdmin4 was also configured as the root account password on the operating system. From charlie's interactive SSH session, running 'su - root' with the recovered credential immediately produced a root shell with no further exploitation required. Both flags were read from disk, confirming complete system compromise.

Why it works

Enforce an organisational policy explicitly prohibiting password reuse across database accounts, service accounts, and OS user accounts. Deploy a privileged access management (PAM) solution to generate and rotate unique, high-entropy passwords for each account class. For root access specifically, disable password-based su and sudo and require SSH key-based authentication or time-limited PAM-brokered elevation. Immediately change the root password and all other accounts sharing the compromised value, and audit access logs for any prior unauthorised use.

Findings

Sanitise all user input in the fortune CGI application before passing it to the operating systemCritical
The /select endpoint concatenated the user-supplied 'db' parameter directly into a shell command with no validation, allowing any unauthenticated visitor to run arbitrary OS commands simply by appending a semicolon to the parameter value.
Remove the intermediate CA private key from the web server and restrict it to a dedicated offline signing hostCritical
The intermediate Certificate Authority private key was stored on the web server filesystem with permissions that allowed the _fortune web service account to read it. Any attacker who gained code execution — even as the low-privilege web process — could immediately read and export the key, then forge client certificates trusted by the server's mTLS endpoint.
Enforce Kerberos-authenticated NFS and restrict the /home export to specific trusted management hostsHigh
The NFS /home export used AUTH_SYS (UNIX) authentication, which trusts the numeric UID declared by the connecting client machine without any cryptographic verification. Any host able to reach the NFS port could mount the share and read or write any user's files by simply running as a local account with the target UID.
Do not store database credentials inside pgAdmin4; use a secrets manager or per-user pgpass filesHigh
pgAdmin4 stored the PostgreSQL DBA password encrypted with AES-CFB8 using a key derived from a predictable application-level constant with no per-installation randomness. Anyone who could read the pgadmin4.db SQLite file — including charlie, who had no legitimate need for the DBA credential — could reverse the encryption offline using publicly documented tooling.
Enforce unique passwords for every account and prohibit reuse of service or database credentials as OS account passwordsCritical
The PostgreSQL DBA password recovered from pgAdmin4 was identical to the root account password on the operating system. Once the database credential was obtained through an unrelated attack path, the attacker required no additional exploitation to gain full OS control.

Exposed services

External surface