CVE-2024-1728 (Python, published 2024-04-10T17:15Z)
Description: value: gradio-app/gradio is vulnerable to a local file inclusion vulnerability due to improper validation of user-supplied input in the UploadButton component. Attackers can exploit this vulnerability to read arbitrary files on the filesystem, such as private SSH keys, by manipulating the file path in the request to the `/queue/join` endpoint. This issue could potentially lead to remote code execution. The vulnerability is present in the handling of file upload paths, allowing attackers to redirect file uploads to unintended locations on the server.
Severity: nan (CVSS v3 base score: )
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/gradio-app/gradio
Files changed:
.changeset/moody-impalas-rule.md
gradio/blocks.py
gradio/processing_utils.py
gradio/routes.py
gradio/utils.py
Commit message: Prevent components from working with non-uploaded files (#7465)

* changes

* chanegs

* add changeset

* changes

* changes

* add changeset

* changes

* changes

* changes

---------

Co-authored-by: Ali Abid <aliabid94@gmail.com>
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>

Vulnerable code:
import tempfile
        self.GRADIO_CACHE = str(
            Path(
                os.environ.get("GRADIO_TEMP_DIR")
                or str(Path(tempfile.gettempdir()) / "gradio")
            ).resolve()
        )
        self, fn_index: int, inputs: list[Any], state: SessionState | None
                        inputs[i], block, add_urls=True
    def run_fn_batch(self, fn, batch, fn_index, state):
        return [fn(fn_index, list(i), state) for i in zip(*batch)]
                    self.preprocess_data, fn_index, inputs, state, limiter=self.limiter
from gradio.utils import abspath
import tempfile
from gradio.utils import (
    get_package_version,
)
        self.uploaded_file_dir = os.environ.get("GRADIO_TEMP_DIR") or str(
            (Path(tempfile.gettempdir()) / "gradio").resolve()
        )

Fixed code:
---
"gradio": patch
---

feat:Prevent components from working with non-uploaded files
    get_upload_folder,
        self.GRADIO_CACHE = get_upload_folder()
            explicit_call=True,
        self,
        fn_index: int,
        inputs: list[Any],
        state: SessionState | None,
        explicit_call: bool = False,
                        inputs[i],
                        block,
                        add_urls=True,
                        check_in_upload_folder=not explicit_call,
    def run_fn_batch(self, fn, batch, fn_index, state, explicit_call=None):
        output = []
        for i in zip(*batch):
            args = [fn_index, list(i), state]
            if explicit_call is not None:
                args.append(explicit_call)
            output.append(fn(*args))
        return output
        explicit_call: bool = False,
            in_event_listener: whether this API call is being made in response to an event listener
            explicit_call: whether this call is being made directly by calling the Blocks function, instead of through an event listener or API route
                explicit_call,
                    self.preprocess_data,
                    fn_index,
                    inputs,
                    state,
                    explicit_call,
                    limiter=self.limiter,
from gradio.utils import abspath, get_upload_folder, is_in_or_equal
    check_in_upload_folder=False,
        add_urls: Whether to add URLs to the payload
        check_in_upload_folder: If True, instead of moving the file to cache, checks if the file is in already in cache (exception if not).
            if check_in_upload_folder and not client_utils.is_http_url_like(
                payload.path
            ):
                path = os.path.abspath(payload.path)
                if not is_in_or_equal(path, get_upload_folder()):
                    raise ValueError(
                        f"File {path} is not in the upload folder and cannot be accessed."
                    )
from gradio.utils import get_package_version, get_upload_folder
        self.uploaded_file_dir = get_upload_folder()
import tempfile


def get_upload_folder() -> str:
    return os.environ.get("GRADIO_TEMP_DIR") or str(
        (Path(tempfile.gettempdir()) / "gradio").resolve()
    )