CVE-2023-6753 (Python, published 2023-12-13T00:15Z)
Description: value: Path Traversal in GitHub repository mlflow/mlflow prior to 2.9.2.
Severity: nan (CVSS v3 base score: 8.8)
Weakness: CWE-22 \u2014 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
Repository: https://github.com/mlflow/mlflow
Files changed:
mlflow/data/http_dataset_source.py
tests/data/test_http_dataset_source.py
Commit message: Fix for windows traversal attack (#10647)

Signed-off-by: Ben Wilson <benjamin.wilson@databricks.com>
Signed-off-by: Ben Wilson <39283302+BenWilson2@users.noreply.github.com>
Co-authored-by: Harutaka Kawamura <hkawamura0130@gmail.com>

Vulnerable code:
import posixpath
        path = urlparse(self.url).path
        content_disposition = resp.headers.get("Content-Disposition")
        if content_disposition is not None and (
            file_name := next(re.finditer(r"filename=(.+)", content_disposition), None)
        ):
            # NB: If the filename is quoted, unquote it
            basename = file_name[1].strip("'\"")
            if _is_path(basename):
                raise MlflowException.invalid_parameter_value(
                    f"Invalid filename in Content-Disposition header: {basename}. "
                    "It must be a file name, not a path."
                )
        elif path is not None and len(posixpath.basename(path)) > 0:
            basename = posixpath.basename(path)
        else:

Fixed code:
    def _extract_filename(self, response) -> str:
        """
        Extracts a filename from the Content-Disposition header or the URL's path.
        """
        if content_disposition := response.headers.get("Content-Disposition"):
            for match in re.finditer(r"filename=(.+)", content_disposition):
                filename = match[1].strip("'\"")
                if _is_path(filename):
                    raise MlflowException.invalid_parameter_value(
                        f"Invalid filename in Content-Disposition header: {filename}. "
                        "It must be a file name, not a path."
                    )
                return filename

        # Extract basename from URL if no valid filename in Content-Disposition
        return os.path.basename(urlparse(self.url).path)

        basename = self._extract_filename(resp)

        if not basename:
from mlflow.utils.os import is_windows


@pytest.mark.skipif(not is_windows(), reason="This test only passes on Windows")
@pytest.mark.parametrize(
    "filename",
    [
        r"..\..\poc.txt",
        r"Users\User\poc.txt",
    ],
)
def test_source_load_with_content_disposition_header_invalid_filename_windows(filename):
    def download_with_mock_content_disposition_headers(*args, **kwargs):
        response = cloud_storage_http_request(*args, **kwargs)
        response.headers = {"Content-Disposition": f"attachment; filename={filename}"}
        return response

    with mock.patch(
        "mlflow.data.http_dataset_source.cloud_storage_http_request",
        side_effect=download_with_mock_content_disposition_headers,
    ):
        source = HTTPDatasetSource(
            "https://raw.githubusercontent.com/mlflow/mlflow/master/tests/datasets/winequality-red.csv"
        )

        # Expect an MlflowException for invalid filenames
        with pytest.raises(MlflowException, match="Invalid filename in Content-Disposition header"):
            source.load()