Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions mlflow/CVE-2026-0545/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM python:3.10-slim

WORKDIR /app

# Install a vulnerable version of MLflow (3.9.0 or earlier)
RUN pip install "mlflow[auth]==3.9.0" uvicorn

# Copy necessary configuration and code
COPY basic_auth.ini /app/
COPY demo_job.py /app/
COPY start.sh /app/

# Make the start script executable
RUN chmod +x /app/start.sh

# Expose the MLflow server port
EXPOSE 5590

# Start the MLflow server via the wrapper script
CMD ["/app/start.sh"]
107 changes: 107 additions & 0 deletions mlflow/CVE-2026-0545/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# CVE-2026-0545: MLflow Basic Auth Bypass & Unauthenticated Job Execution

MLflow contains an access control vulnerability in its job execution subsystem (`MLFLOW_SERVER_ENABLE_JOB_EXECUTION`). When job execution is enabled alongside basic authentication on MLflow servers, authorization checks fail to properly restrict unauthenticated or low-privileged HTTP requests to job management endpoints (e.g., `/ajax-api/3.0/jobs/`). This weakness allows unauthorized users to trigger pre-configured job functions and execute underlying commands within the server container context.

---

## Overview

| Attribute | Details |
| :--- | :--- |
| **CVE ID** | CVE-2026-0545 |
| **Vulnerability Type** | Incorrect Authorization / Improper Access Control (CWE-284) |
| **Vulnerable Versions** | `mlflow[auth] <= 3.9.0` |
| **Patched / Fixed Versions** | `mlflow[auth] >= 3.9.1` |
| **Impact** | Remote Code Execution / Unauthorized Job Execution |

---

## Environment Setup

To deploy the vulnerable environment for testing or verification in an isolated laboratory context, use the following deployment configuration files.

### Configuration Files

#### `basic_auth.ini`
```ini
[mlflow]
default_permission = NO_PERMISSIONS
database_uri = sqlite:///basic_auth.db
admin_username = admin
admin_password = password1234
authorization_function = mlflow.server.auth:authenticate_request_basic_auth
```

#### `demo_job.py`
```python
from mlflow.server.jobs import job
import subprocess

@job(name="run_task", max_workers=1)
def run_task(command="id"):
return subprocess.check_output(command, shell=True).decode()
```

#### `start.sh`
```bash
#!/bin/bash

# Configuration for MLflow Basic Auth
export MLFLOW_AUTH_CONFIG_PATH=/app/basic_auth.ini
export MLFLOW_FLASK_SERVER_SECRET_KEY=test-secret-key

# Enable vulnerable job execution settings
export MLFLOW_SERVER_ENABLE_JOB_EXECUTION=true
export _MLFLOW_SUPPORTED_JOB_FUNCTION_LIST=demo_job.run_task
export _MLFLOW_ALLOWED_JOB_NAME_LIST=run_task
export PYTHONPATH=/app

mkdir -p /app/artifacts

exec mlflow server \
--app-name=basic-auth \
--host 0.0.0.0 \
--port 5590 \
--backend-store-uri sqlite:///backend.db \
--default-artifact-root /app/artifacts
```

#### `Dockerfile`
```dockerfile
FROM python:3.10-slim

WORKDIR /app

# Install vulnerable MLflow version
RUN pip install "mlflow[auth]==3.9.0" uvicorn

COPY basic_auth.ini /app/
COPY demo_job.py /app/
COPY start.sh /app/

RUN chmod +x /app/start.sh

EXPOSE 5590

CMD ["/app/start.sh"]
```

#### `docker-compose.yml`
```yaml
version: '3.8'

services:
mlflow-server:
build:
context: .
dockerfile: Dockerfile
container_name: mlflow_app
ports:
- "5590:5590"
volumes:
- mlflow_data:/app/artifacts
restart: unless-stopped

volumes:
mlflow_data:
```
Comment on lines +23 to +107

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can instead say checkout the files and run the docker command to deploy vulnerable image. Also document how to deploy a non vulnerable version

6 changes: 6 additions & 0 deletions mlflow/CVE-2026-0545/basic_auth.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[mlflow]
default_permission = NO_PERMISSIONS
database_uri = sqlite:///basic_auth.db
admin_username = admin
admin_password = password1234
authorization_function = mlflow.server.auth:authenticate_request_basic_auth
6 changes: 6 additions & 0 deletions mlflow/CVE-2026-0545/demo_job.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from mlflow.server.jobs import job
import subprocess

@job(name="run_task", max_workers=1)
def run_task(command="id"):
return subprocess.check_output(command, shell=True).decode()
17 changes: 17 additions & 0 deletions mlflow/CVE-2026-0545/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: '3.8'

services:
mlflow-server:
build:
context: .
dockerfile: Dockerfile
container_name: mlflow_app
ports:
- "5590:5590"
volumes:
# Only keep the persistent data storage mount
- mlflow_data:/app/artifacts
restart: unless-stopped

volumes:
mlflow_data:
20 changes: 20 additions & 0 deletions mlflow/CVE-2026-0545/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash

# Configuration for MLflow Basic Auth
export MLFLOW_AUTH_CONFIG_PATH=/app/basic_auth.ini
export MLFLOW_FLASK_SERVER_SECRET_KEY=test-secret-key

# Requirements to trigger the bypass
export MLFLOW_SERVER_ENABLE_JOB_EXECUTION=true
export _MLFLOW_SUPPORTED_JOB_FUNCTION_LIST=demo_job.run_task
export _MLFLOW_ALLOWED_JOB_NAME_LIST=run_task
export PYTHONPATH=/app

mkdir -p /app/artifacts

exec mlflow server \
--app-name=basic-auth \
--host 0.0.0.0 \
--port 5590 \
--backend-store-uri sqlite:///backend.db \
--default-artifact-root /app/artifacts