CVE-2024-48052
Gradio SSRF in DownloadButton allows local resource access.
- CVSS 6.5
- CWE-918
- Configuration Issues
- Remote
In gradio <=4.42.0, the gr.DownloadButton function has a hidden server-side request forgery (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. This can lead to the download of local resources and sensitive information.
- CWE
- CWE-918
- CVSS base score
- 6.5
- Published
- 2024-11-04
- OWASP
- A10 Server-Side Request Forgery (SSRF)
- Orthogonal defect classification
- Interface
- Code defect classification
- Incorrect Functionality
- Category
- Configuration Issues
- Subcategory
- Server-Side Request Forgery (SSRF)
- Accessibility scope
- Remote
- Impact
- Information Disclosure
- Affected component
- Gradio
- Fixed by upgrading
- Yes
Solution
Upgrade to gradio>=4.43.0
Vulnerable code sample
import gradio as gr
import requests
import os
def save_url_to_cache(url, filename):
"""
Saves the content from a given URL to a local file.
Args:
url: The URL to fetch content from.
filename: The name of the file to save the content to.
"""
try:
response = requests.get(url, stream=True)
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
with open(filename, 'wb') as file:
for chunk in response.iter_content(chunk_size=8192):
file.write(chunk)
return filename # Return the filename to confirm success
except requests.exceptions.RequestException as e:
return f"Error downloading file: {e}"
def download_button_example(url):
"""
Example function to demonstrate the download button with a provided URL.
"""
filename = "downloaded_file.txt" # Fixed filename for demonstration
filepath = save_url_to_cache(url, filename)
if "Error" in filepath:
return filepath # Return error message
return filepath # Return path to the downloaded file, which will trigger Gradio to offer it as a download
if __name__ == "__main__":
with gr.Blocks() as demo:
url_input = gr.Textbox(label="Enter URL to download")
download_output = gr.Textbox(label="Downloaded File Path")
download_button = gr.Button("Download")
download_button.click(fn=download_button_example, inputs=url_input, outputs=download_output)
demo.launch()Patched code sample
import urllib.parse
import requests
import os
def save_url_to_cache_fixed(url, cache_dir="."):
"""
Downloads a file from a URL and saves it to a local cache,
with SSRF protection.
"""
try:
parsed_url = urllib.parse.urlparse(url)
# SSRF Protection: Check allowed schemes and domains
allowed_schemes = ["http", "https"]
allowed_domains = ["example.com", "example.org", "gradio.app"] # Replace with your allowed domains
if parsed_url.scheme not in allowed_schemes:
raise ValueError(f"Invalid scheme: {parsed_url.scheme}. Allowed schemes are: {allowed_schemes}")
if parsed_url.hostname not in allowed_domains:
raise ValueError(f"Invalid domain: {parsed_url.hostname}. Allowed domains are: {allowed_domains}")
# Further validation - prevent file:/// or other local access schemes slipping through.
if parsed_url.scheme not in ('http', 'https'):
raise ValueError("Only http or https URLs are allowed.")
# Prevent path traversal attacks. Crucially important.
# This is an extra layer on top of scheme/domain validation.
if ".." in parsed_url.path:
raise ValueError("Path traversal detected.")
response = requests.get(url, stream=True)
response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
filename = os.path.basename(parsed_url.path)
filepath = os.path.join(cache_dir, filename)
with open(filepath, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
return filepath
except Exception as e:
print(f"Error downloading {url}: {e}")
return None
if __name__ == '__main__':
# Example usage
# Replace with a URL that *should* be allowed based on the filters above
allowed_url = "https://example.com/somefile.txt"
cached_file = save_url_to_cache_fixed(allowed_url)
if cached_file:
print(f"File downloaded and cached at: {cached_file}")
# Example of a blocked URL (SSRF protection)
blocked_url = "file:///etc/passwd" # Simulate SSRF attempt
cached_file = save_url_to_cache_fixed(blocked_url) # This should throw error because of file:/// scheme
# Example of a blocked URL (SSRF protection, different domain)
blocked_url = "https://malicious.com/somefile.txt" # Simulate SSRF attempt
cached_file = save_url_to_cache_fixed(blocked_url) # This should throw error because of unallowed domain
# Example of a blocked URL (Path traversal protection)
blocked_url = "https://example.com/../../etc/passwd"
cached_file = save_url_to_cache_fixed(blocked_url)Payload
`file:///etc/passwd`
Cite this entry
@misc{vaitp:cve202448052,
title = {{Gradio SSRF in DownloadButton allows local resource access.
}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2024},
note = {VAITP Python Vulnerability Dataset, entry CVE-2024-48052},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2024-48052/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
