This post documents the complete walkthrough of Fortune, a retired vulnerable VM created by AuxSarge, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.

On this post

Background

Fortune is a retired vulnerable VM from Hack The Box.

Information Gathering

Let’s start with a masscan probe to establish the open ports in the host.

# masscan -e tun0 -p1-65535,U:1-65535 10.10.10.127 --rate=700 Starting masscan 1.0.4 (http://bit.ly/14GZzcT) at 2019-03-11 00:47:55 GMT -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth Initiating SYN Stealth Scan Scanning 1 hosts [131070 ports/host] Discovered open port 80/tcp on 10.10.10.127 Discovered open port 22/tcp on 10.10.10.127 Discovered open port 443/tcp on 10.10.10.127

masscan finds the three open ports. Let’s do one better with nmap scanning the discovered ports.

# nmap -n -v -Pn -p22,80,443 -A --reason -oN nmap.txt 10.10.10.127 ... PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack ttl 63 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 syn-ack ttl 63 OpenBSD httpd | http-methods: |_ Supported Methods: GET HEAD |_http-server-header: OpenBSD httpd |_http-title: Fortune 443/tcp open ssl/https? syn-ack ttl 63 |_ssl-date: TLS randomness does not represent time

Feels good to be back in the Unix/Linux environment again. Let’s start with the http service. This is how it looks like.

Let’s select the recipes database and submit it. I got Burp switched on to capture the POST request.

The POST request is simple enough. What if we replicate this behavior with wfuzz and a bunch of weird characters?

I think we got command injection in our hands!

Client-side SSL Certificate

I was very eager to run a reverse shell back but it seems the firewall is blocking outbound communications. During enumeration of the SSH configuration, I noticed the following.

Match User nfsuser AuthorizedKeysFile none AuthorizedKeysCommand /usr/local/bin/psql -Aqt -c "SELECT key from authorized_keys where uid = '%u';" authpf appsrv AuthorizedKeysCommandUser _sshauth

It seems like authpf is installed. According to the FAQ,

The authpf(8) utility is a user shell for authenticating gateways. An authenticating gateway is just like a regular network gateway (also known as a router) except that users must first authenticate themselves to it before their traffic is allowed to pass through.

Somewhere further down the enumeration road, I also noticed the following listening ports but they didn’t appear in the port scan.

Putting on my investigator’s hat, I soon discovered the following http services.

A few more pieces of the puzzle tell me that everything points towards getting a client-side certificate signed:

The SSH key-pair generation code is at /var/appsrv/sshauth/sshauthd.py

The intermediate CA’s certificate and key is present at /home/bob/ca/intermediate

The /home directory is exported via NFS.

SSH key-pair generation and insertion into the database

Intermediate CA

The /home directory exported via NFS

I made the initial mistake of thinking that I could insert my own public key (that I control) into the authpf database, and wasted precious time. All because I saw this.

See? appsrv can select, insert and update on authpf . Well, for consolation, bob can view the table authorized_keys without password. At least, I can tell if the generated public key and my IP address got inserted into the table or not.

…

Back to the main topic of accessing https://fortune.htb/generate , I need to generate a Certificate Signing Request (or CSR). Well, I can easily copy the Intermediate CA’s certificate and private key from Burp.

Generate my private key and CSR with OpenSSL

# openssl genrsa -out me.key # openssl req -new -key me.key -out me.csr

It doesn’t matter what values I use for the CSR as long as I have the Intermediate CA vouching for my trust-worthiness with its certificate and key.

Generate client certificate and signed by Intermediate CA

# openssl x509 -req -in me.csr -CA intermediate.cert.pem -CAkey intermediate.key.pem -CAcreateserial -out me.pem

I also need to combine my private key and my certificate to PKCS#12 format because that’s what Firefox accepts without questioning.

# openssl pcks12 -export -out me.p12 -in me.pem -inkey me.key

Let’s import my certificate into Firefox!

Before import

After import

Time to generate SSH key-pair for nfsuser .

Copy-and-paste the key-pair to nfsuser and nfuser.pub respectively and SSH-in to open the gateway.

Here’s a new round of nmap scan results.

PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack ttl 63 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 syn-ack ttl 63 OpenBSD httpd | http-methods: |_ Supported Methods: GET HEAD |_http-server-header: OpenBSD httpd |_http-title: Fortune 111/tcp open rpcbind syn-ack ttl 63 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 756/udp mountd |_ 100005 1,3 1012/tcp mountd 443/tcp open ssl/https? syn-ack ttl 63 |_ssl-date: TLS randomness does not represent time 1012/tcp open mountd syn-ack ttl 63 1-3 (RPC #100005) 2049/tcp open nfs syn-ack ttl 63 2-3 (RPC #100003) 8081/tcp open http syn-ack ttl 63 OpenBSD httpd | http-methods: |_ Supported Methods: GET HEAD |_http-server-header: OpenBSD httpd |_http-title: pgadmin4

Awesome.

Low-Privilege Shell

Since we can mount /home as anyone, let’s mount it as charlie so that we can copy SSH public key we control (finally…) to authorized_keys .

And bam…we have a shell.

The user.txt is at the home directory of charlie .

Privilege Escalation

During enumeration of charlie ’s account, I notice bob has sent charlie an email.

It clearly stated that bob has set the dba ’s password to root ’s password. Seeing that drove me to hunt for dba ’s password.

Long story short. The dba ’s password to the PostgreSQL database is kept in a SQLite3 database located at /var/appsrv/pgadmin4/pgadmin4.db .

If you look at this code, you’ll see that the decryption key is from the user.password , which can also be found in the SQLite3 database, pgadmin4.db .

The decrypt function is pretty simple.

def decrypt ( ciphertext , key ): """ Decrypt the AES encrypted string. Parameters: ciphertext -- Encrypted string with AES method. key -- key to decrypt the encrypted string. """ ciphertext = base64 . b64decode ( ciphertext ) iv = ciphertext \ [: iv_size \ ] cipher = Cipher ( AES ( pad ( key )), CFB8 ( iv ), default_backend ()) decryptor = cipher . decryptor () return decryptor . update ( ciphertext \ [ iv_size : \ ]) + decryptor . finalize () def pad ( key ): """Add padding to the key.""" if isinstance ( key , six . text_type ): key = key . encode () # Key must be maximum 32 bytes long, so take first 32 bytes key = key \ [: 32 \ ] # If key size is 16, 24 or 32 bytes then padding is not required if len ( key ) in ( 16 , 24 , 32 ): return key # Add padding to make key 32 bytes long return key . ljust ( 32 , padding_string )

The parameters are all in pgadmin4.db .

Encrypted password

User’s password hash as key

Grab a copy of crypto.py from pgAdmin’s GitHub repository and append the following code to it.

encpass = 'utUU0jkamCZDmqFLOrAuPjFxL0zp8zWzISe5MF0GY/l8Silrmu3caqrtjaVjLQlvFFEgESGz' bob = '$pbkdf2-sha512$25000$z9nbm1Oq9Z5TytkbQ8h5Dw$Vtx9YWQsgwdXpBnsa8BtO5kLOdQGflIZOQysAy7JdTVcRbv/6csQHAJCAIJT9rLFBawClFyMKnqKNL5t3Le9vg' charlie = '$pbkdf2-sha512$25000$3hvjXAshJKQUYgxhbA0BYA$iuBYZKTTtTO.cwSvMwPAYlhXRZw8aAn9gBtyNQW3Vge23gNUMe95KqiAyf37.v1lmCunWVkmfr93Wi6.W.UzaQ' print decrypt ( encpass , bob ) print decrypt ( encpass , charlie )