PoC Archive PoC Archive
Critical CVE-2026-0596 (GHSA-rvhj-8chj-8v3c) unpatched

MLflow / MLServer Insecure Pickle Deserialization RCE — CVE-2026-0596

by SparshBiswas-AI · 2026-07-05

CVSS 9.6/10
Severity
Critical
CVE
CVE-2026-0596 (GHSA-rvhj-8chj-8v3c)
Category
web
Affected product
MLflow model serving stack (mlflow==2.11.1, mlserver==1.3.5, mlserver-mlflow==1.3.5)
Affected versions
mlflow 2.11.1 and mlserver 1.3.5 deployed with enable_mlserver=True (legacy pickle-based model artifacts)
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-05
Author / ResearcherSparshBiswas-AI
CVE / AdvisoryCVE-2026-0596 (GHSA-rvhj-8chj-8v3c)
Categoryweb
SeverityCritical
CVSS Score9.6 Critical (huntr.dev CNA) / 7.8 High (NVD)
StatusPoC
Tagsmlflow, mlserver, pickle, insecure-deserialization, rce, machine-learning, cwe-502, docker
RelatedN/A

Affected Target

FieldValue
Software / SystemMLflow model serving stack (mlflow==2.11.1, mlserver==1.3.5, mlserver-mlflow==1.3.5)
Versions Affectedmlflow 2.11.1 and mlserver 1.3.5 deployed with enable_mlserver=True (legacy pickle-based model artifacts)
Language / PlatformPython 3.10, containerized via Docker
Authentication RequiredNo
Network Access RequiredYes (model-serving REST endpoint) / Local-equivalent when loading a poisoned artifact

Summary

MLflow can serve models through Seldon’s MLServer runtime, which loads model artifacts using Python’s native pickle format. While the REST API’s string parameters are handled safely and are not vulnerable to classic OS command injection, the underlying artifact-loading path calls pickle.load() on model files without any restriction on the object graph being reconstructed. An attacker who can supply or substitute a model artifact can embed a __reduce__ hook that invokes os.system (or any other callable) the instant the object is deserialized, achieving arbitrary code execution inside the serving container before any inference logic runs. The included lab first proves that shell metacharacter injection into API parameters is not exploitable, then demonstrates the real vulnerability using a crafted pickle payload.


Vulnerability Details

Root Cause

pickle.load() executes attacker-controlled bytecode-equivalent instructions during object reconstruction; a class implementing __reduce__ can force execution of an arbitrary callable (e.g., os.system) as soon as the “model” file is unpickled by the serving pipeline.

Attack Vector

  1. Attacker crafts a Python object whose __reduce__ method returns (os.system, (command,)).
  2. The object is serialized with pickle.dump() into a file that mimics a legitimate model artifact (e.g., vulnerable_model.pkl).
  3. The malicious artifact is delivered to, or substituted within, the model storage/tracking path used by the MLflow/MLServer deployment.
  4. When MLflow/MLServer loads the artifact via pickle.load() (directly or through mlflow.pyfunc internals), the embedded command executes immediately inside the serving container.

Impact

Full arbitrary code execution within the model-serving container context, enabling data exfiltration, lateral movement, or persistence in ML infrastructure — with no authentication or user interaction required beyond the model being loaded.


Environment / Lab Setup

Target:   Docker container running python:3.10-slim with mlflow==2.11.1, mlserver==1.3.5, mlserver-mlflow==1.3.5 (Dockerfile provided)
Attacker: Python 3.10 with pickle (stdlib) to craft the payload; docker cp / docker exec to stage and trigger it inside the sandbox

Proof of Concept

PoC Script

See Dockerfile, docker-compose.yml, generate_model.py, trigger_native.py, and verify_poc.py in this folder.

1
2
3
4
docker compose up -d --build
docker cp trigger_native.py mlflow_sandbox:/app/trigger_native.py
docker exec -it mlflow_sandbox python /app/trigger_native.py
python verify_poc.py

generate_model.py builds a baseline MLflow pyfunc model inside the container. trigger_native.py serializes an ExploitModel whose __reduce__ hook calls os.system("touch /tmp/native_success_marker.txt"), then immediately unpickles it to simulate the server’s own loading path. verify_poc.py checks that the marker file was created, confirming code execution occurred during deserialization.


Detection & Indicators of Compromise

Signs of compromise:

  • Model-serving process spawning shell or system binaries shortly after an artifact download/load event
  • Unexplained files created in the container’s temp directory correlating with model load timestamps
  • Model artifacts containing non-standard pickle opcodes or unexpected classes when inspected with pickletools

Remediation

ActionDetail
Primary fixNo vendor patch confirmed as of 2026-07-05 — monitor mlflow/mlserver advisories; migrate model formats away from pickle
Interim mitigationUse Safetensors or ONNX for model storage instead of pickle/joblib/marshal; run serving containers as non-root with read-only filesystems and dropped capabilities; restrict artifact upload/storage access

References


Notes

Mirrored from https://github.com/SparshBiswas-AI/CVE-2026-0596-Reproduction on 2026-07-05.

generate_model.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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):
        print("[*] Generating minimal MLflow pyfunc model...")
        mlflow.pyfunc.save_model(
            path=model_path,
            python_model=DummyModel()
        )
        print(f"[+] Model successfully saved to {model_path}")
    else:
        print("[*] Model directory already exists.")