Skip to content

[Bug] [dbgpt-app] Unauthenticated path-traversal arbitrary file write via the user_id header in the python file-upload endpoint #3104

Description

@geo-chen

Search before asking

  • I had searched in the issues and found no similar issues.

Operating system information

Linux

Python version information

=3.11

DB-GPT version

main

Related scenes

  • Chat Data
  • Chat Excel
  • Chat DB
  • Chat Knowledge
  • Model Management
  • Dashboard
  • Plugins

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?

  • Yes I am willing to submit a PR!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions