The ChatDev workflow server saves uploaded files to a path built by joining a freshly created temporary directory with the client-supplied multipart filename, with no sanitization. A filename containing parent-directory segments escapes the temporary directory and writes attacker-controlled bytes to an arbitrary location on the host. The function's cleanup step then unlinks the same traversed path, so the upload also deletes an arbitrary file. The endpoint requires only a known session id, which is minted without authentication. Confirmed by calling the real save_upload_file with a traversal filename: the file was written outside the temp directory, and an existing victim file was deleted.
Details
server/services/attachment_service.py (save_upload_file, around lines 46 to 80):
filename = upload.filename or "upload.bin"
temp_dir = Path(tempfile.mkdtemp(prefix="mac_upload_"))
temp_path = temp_dir / filename # filename not sanitized
with temp_path.open("wb") as buffer:
... shutil.copyfileobj(upload.file, buffer)
... register_file(...) # basenames it later (too late)
finally:
temp_path.unlink() # follows the same traversal -> arbitrary delete
temp_dir / filename with filename = "../../../../tmp/CHATDEV_PWNED" resolves outside the mkdtemp directory. The later register_file uses the basename, but the write at temp_path.open("wb") already happened with the raw path. The finally block's unlink() resolves the same traversal, so any existing file the process can reach is deleted.
This is reached by POST /api/uploads/{session_id} (server/routes/uploads.py), which forwards the raw Starlette UploadFile (Starlette / python-multipart preserve / and .. in the filename). The route requires a known session via ensure_known_session(..., require_connection=False), but there is no authentication: a session id is minted by opening the workflow websocket or hitting the workflow-execute endpoints, with no credentials. The sibling download route validates session_id with a regex; the upload route validates neither the session id nor the filename.
Impact
A client that has minted a session id (no authentication) can write attacker-controlled files to, and delete arbitrary files at, any existing-directory path the server process can reach: overwrite shell rc / cron files / served static assets for code execution, or destroy host files via the cleanup unlink. Effective impact is arbitrary file write plus arbitrary file delete on the host.
The ChatDev workflow server saves uploaded files to a path built by joining a freshly created temporary directory with the client-supplied multipart filename, with no sanitization. A filename containing parent-directory segments escapes the temporary directory and writes attacker-controlled bytes to an arbitrary location on the host. The function's cleanup step then unlinks the same traversed path, so the upload also deletes an arbitrary file. The endpoint requires only a known session id, which is minted without authentication. Confirmed by calling the real
save_upload_filewith a traversal filename: the file was written outside the temp directory, and an existing victim file was deleted.Details
server/services/attachment_service.py(save_upload_file, around lines 46 to 80):temp_dir / filenamewithfilename = "../../../../tmp/CHATDEV_PWNED"resolves outside the mkdtemp directory. The laterregister_fileuses the basename, but the write attemp_path.open("wb")already happened with the raw path. Thefinallyblock'sunlink()resolves the same traversal, so any existing file the process can reach is deleted.This is reached by
POST /api/uploads/{session_id}(server/routes/uploads.py), which forwards the raw StarletteUploadFile(Starlette / python-multipart preserve/and..in the filename). The route requires a known session viaensure_known_session(..., require_connection=False), but there is no authentication: a session id is minted by opening the workflow websocket or hitting the workflow-execute endpoints, with no credentials. The sibling download route validatessession_idwith a regex; the upload route validates neither the session id nor the filename.Impact
A client that has minted a session id (no authentication) can write attacker-controlled files to, and delete arbitrary files at, any existing-directory path the server process can reach: overwrite shell rc / cron files / served static assets for code execution, or destroy host files via the cleanup unlink. Effective impact is arbitrary file write plus arbitrary file delete on the host.