CVE-2023-6974 (Python, published 2023-12-20T06:15Z)
Description: value: A malicious user could use this issue to access internal HTTP(s) servers and in the worst case (ie: aws instance) it could be abuse to get a remote code execution on the victim machine.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: CWE-918 \u2014 Server-Side Request Forgery (SSRF)
The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
Repository: https://github.com/mlflow/mlflow
Files changed:
mlflow/environment_variables.py
mlflow/utils/request_utils.py
tests/projects/test_databricks.py
tests/store/artifact/test_databricks_artifact_repo.py
tests/store/tracking/test_rest_store.py
tests/utils/test_request_utils.py
tests/utils/test_rest_utils.py
Commit message: Add env var to disable redirects again (#10673)

Signed-off-by: Daniel Lok <daniel.lok@databricks.com>

Vulnerable code:
    return session.request(method, url, **kwargs)
            "put", MOCK_AWS_SIGNED_URI, data=ANY, headers={}, timeout=None
            "put", MOCK_AWS_SIGNED_URI, data=ANY, headers=expected_headers, timeout=None
            "put", MOCK_GCP_SIGNED_URL, data=ANY, headers={}, timeout=None
            "put", MOCK_GCP_SIGNED_URL, data=ANY, headers=expected_headers, timeout=None

Fixed code:

#: Specifies whether or not to allow the MLflow server to follow redirects when
#: making HTTP requests. If set to False, the server will throw an exception if it
#: encounters a redirect response.
#: (default: ``True``)
MLFLOW_ALLOW_HTTP_REDIRECTS = _BooleanEnvironmentVariable("MLFLOW_ALLOW_HTTP_REDIRECTS", True)
    allow_redirects=None,

    # the environment variable is hardcoded here to avoid importing mlflow.
    # however, documentation is available in environment_variables.py
    env_value = os.getenv("MLFLOW_ALLOW_HTTP_REDIRECTS", "true").lower() in ["true", "1"]
    allow_redirects = env_value if allow_redirects is None else allow_redirects

    return session.request(method, url, allow_redirects=allow_redirects, **kwargs)
            "allow_redirects": True,
            allow_redirects=True,
            allow_redirects=True,
            allow_redirects=True,
            allow_redirects=True,
            allow_redirects=True,
            allow_redirects=True,
                allow_redirects=True,
                allow_redirects=True,
            "put", MOCK_AWS_SIGNED_URI, allow_redirects=True, data=ANY, headers={}, timeout=None
            "put",
            MOCK_AWS_SIGNED_URI,
            allow_redirects=True,
            data=ANY,
            headers=expected_headers,
            timeout=None,
            "put", MOCK_GCP_SIGNED_URL, allow_redirects=True, data=ANY, headers={}, timeout=None
            "put",
            MOCK_GCP_SIGNED_URL,
            allow_redirects=True,
            data=ANY,
            headers=expected_headers,
            timeout=None,
                    allow_redirects=True,
                    allow_redirects=True,
                    allow_redirects=True,
            allow_redirects=True,
            "allow_redirects": True,


@pytest.mark.parametrize("env_value", ["0", "false", "False", "FALSE"])
def test_redirects_disabled_if_env_var_set(monkeypatch, env_value):
    monkeypatch.setenv("MLFLOW_ALLOW_HTTP_REDIRECTS", env_value)

    with mock.patch("requests.Session.request") as mock_request:
        mock_request.return_value.status_code = 302
        mock_request.return_value.text = "mock response"

        response = request_utils.cloud_storage_http_request("GET", "http://localhost:5000")

        assert response.text == "mock response"
        mock_request.assert_called_once_with(
            "GET",
            "http://localhost:5000",
            allow_redirects=False,
            timeout=None,
        )


@pytest.mark.parametrize("env_value", ["1", "true", "True", "TRUE"])
def test_redirects_enabled_if_env_var_set(monkeypatch, env_value):
    monkeypatch.setenv("MLFLOW_ALLOW_HTTP_REDIRECTS", env_value)

    with mock.patch("requests.Session.request") as mock_request:
        mock_request.return_value.status_code = 302
        mock_request.return_value.text = "mock response"

        response = request_utils.cloud_storage_http_request(
            "GET",
            "http://localhost:5000",
        )

        assert response.text == "mock response"
        mock_request.assert_called_once_with(
            "GET",
            "http://localhost:5000",
            allow_redirects=True,
            timeout=None,
        )


@pytest.mark.parametrize("env_value", ["0", "false", "False", "FALSE"])
def test_redirect_kwarg_overrides_env_value_false(monkeypatch, env_value):
    monkeypatch.setenv("MLFLOW_ALLOW_HTTP_REDIRECTS", env_value)

    with mock.patch("requests.Session.request") as mock_request:
        mock_request.return_value.status_code = 302
        mock_request.return_value.text = "mock response"

        response = request_utils.cloud_storage_http_request(
            "GET", "http://localhost:5000", allow_redirects=True
        )

        assert response.text == "mock response"
        mock_request.assert_called_once_with(
            "GET",
            "http://localhost:5000",
            allow_redirects=True,
            timeout=None,
        )


@pytest.mark.parametrize("env_value", ["1", "true", "True", "TRUE"])
def test_redirect_kwarg_overrides_env_value_true(monkeypatch, env_value):
    monkeypatch.setenv("MLFLOW_ALLOW_HTTP_REDIRECTS", env_value)

    with mock.patch("requests.Session.request") as mock_request:
        mock_request.return_value.status_code = 302
        mock_request.return_value.text = "mock response"

        response = request_utils.cloud_storage_http_request(
            "GET", "http://localhost:5000", allow_redirects=False
        )

        assert response.text == "mock response"
        mock_request.assert_called_once_with(
            "GET",
            "http://localhost:5000",
            allow_redirects=False,
            timeout=None,
        )


def test_redirects_enabled_by_default():
    with mock.patch("requests.Session.request") as mock_request:
        mock_request.return_value.status_code = 302
        mock_request.return_value.text = "mock response"

        response = request_utils.cloud_storage_http_request(
            "GET",
            "http://localhost:5000",
        )

        assert response.text == "mock response"
        mock_request.assert_called_once_with(
            "GET",
            "http://localhost:5000",
            allow_redirects=True,
            timeout=None,
        )
        allow_redirects=True,
        allow_redirects=True,
        allow_redirects=True,
        allow_redirects=True,
        allow_redirects=True,
        allow_redirects=True,
        allow_redirects=True,
        allow_redirects=True,
        allow_redirects=True,
        allow_redirects=True,
            allow_redirects=True,
            allow_redirects=True,
            allow_redirects=True,
        allow_redirects=True,
        allow_redirects=True,


def test_provide_redirect_kwarg():
    with mock.patch("requests.Session.request") as mock_request:
        mock_request.return_value.status_code = 302
        mock_request.return_value.text = "mock response"

        response = http_request(
            MlflowHostCreds("http://my-host"),
            "/my/endpoint",
            "GET",
            allow_redirects=False,
        )

        assert response.text == "mock response"
        mock_request.assert_called_with(
            "GET",
            "http://my-host/my/endpoint",
            allow_redirects=False,
            headers=mock.ANY,
            verify=mock.ANY,
            timeout=120,
        )