## https://sploitus.com/exploit?id=4C0421E1-7649-5C2C-8ABE-23FB9A1E2E2D
# CVE-2026-38360: Directory Traversal in dash-uploader
[](https://vulners.com/cve/CVE-2026-38360)
[](https://cwe.mitre.org/data/definitions/22.html)
[](#)
[](#mitigation)
[](#attack-vectors)
[](https://pypistats.org/packages/dash-uploader)
[](https://pypi.org/project/dash-uploader/)
Unauthenticated directory traversal in [`fohrloop/dash-uploader`](https://github.com/fohrloop/dash-uploader) (Python, PyPI) allowing arbitrary file write, leading to (but not limited to) **Remote Code Execution (RCE)**, application source code overwrite, stored XSS, and persistent backdoor installation.
> [!CAUTION]
> **No patch is available, and none will ever be released.**
>
> The repository was [archived on 2025-07-19](https://github.com/fohrloop/dash-uploader/issues/153) with no active maintainer. Every published version (`0.1.0` through `0.7.0a2`) is affected and will remain so. The package still pulls roughly 28,000 monthly downloads.
>
> Anyone running `dash-uploader` in production must apply a mitigation themselves. The recommended fix is to migrate to Plotly Dash's built-in `dcc.Upload` component. See [Mitigation](#mitigation) for full options.
| | |
|---|---|
| **CVE ID** | CVE-2026-38360 |
| **Vulnerability** | Directory Traversal (CWE-22) |
| **Vendor** | fohrloop (Niko Pasanen) |
| **Product** | dash-uploader |
| **Affected versions** | `0.1.0` through `0.7.0a2` (all 18 releases) |
| **Fixed version** | none (project archived 2025-07-19) |
| **Attack vector** | Remote, unauthenticated |
| **Discoverer** | Muhammad Fitri Bin Mohd Sultan |
| **Assigned by** | MITRE, 2026-05-07 (request `scr2010645`) |
| **Related** | [CVE-2026-38361](https://github.com/draxozbel/CVE-2026-38361) (DoS in same library) |
## Description
Three user-controlled parameters from `request.form.get()` in `dash_uploader/httprequesthandler.py` are passed directly to `os.path.join()` and `os.makedirs()` without any sanitization or validation:
1. **`upload_id`** (line 57 โ line 161, `BaseHttpRequestHandler.get_temp_root`): controls the destination directory. An attacker can send `upload_id=../../../../usr/local/lib/python3.10/site-packages` and files are written to Python's package directory.
2. **`resumableFilename`** (line 51 โ line 108, `BaseHttpRequestHandler._post`): controls the final filename. An attacker can traverse out of the upload directory via the filename even with a legitimate `upload_id`.
3. **`resumableIdentifier`** (line 54 โ line 64, `BaseHttpRequestHandler._post`): used with `os.makedirs()` to create the temp directory. An attacker can create arbitrary directories anywhere on the filesystem.
The upload endpoint (`/API/dash-uploader` by default) requires no authentication. The `http_request_handler` hook added in `v0.5.0` allows pre-request checks via `post_before()`, but the vulnerable `_post()` method reads all parameters directly from `request.form` after the hook returns. The hook cannot sanitize parameters before the library processes them. A developer who adds authentication via the hook is still vulnerable to directory traversal from an authenticated user.
## Attack vectors
An unauthenticated remote attacker sends an HTTP POST multipart request to the upload endpoint. By injecting directory traversal sequences (`../`) into the `upload_id` form parameter, the attacker controls the destination directory for the uploaded file. For example, `upload_id=../../../../usr/local/lib/python3.10/site-packages` writes files to Python's package directory, enabling RCE on the next interpreter startup via `.pth` auto-execution.
No authentication, session token, or CSRF token is required. Two additional parameters (`resumableFilename` and `resumableIdentifier`) provide independent traversal vectors through the same endpoint. The default library configuration as shown in the official quickstart documentation is exploitable with a single `curl` command.
## Impact
Arbitrary file write to any directory writable by the server process. This translates to **Remote Code Execution (RCE)** through several well-known primitives:
**RCE primitives**
- Python `.pth` file dropped into `site-packages`. Executes attacker-supplied code on the next interpreter startup.
- `sitecustomize.py` or `usercustomize.py` injection. Executes on every Python startup.
- Overwriting an importable Python module in the application's package directory. Executes on next import or worker recycle.
- Overwriting the WSGI/ASGI entry point (e.g. `app.wsgi`, `wsgi.py`). Executes on next worker reload.
- Cron drop-in (`/etc/cron.d/`, `/etc/cron.hourly/`, user crontab spool) when the process has the necessary privileges. Scheduled execution.
- Systemd unit or user-unit drop-in (`/etc/systemd/system/`, `~/.config/systemd/user/`). Executes on next service start or reboot.
- `/etc/ld.so.preload` injection when the process runs as root. Preloads attacker code into every subsequent binary execution.
- Shell startup file overwrite (`~/.bashrc`, `~/.profile`, `~/.bash_profile`). Executes on next interactive login of the app user.
- `~/.ssh/authorized_keys` append. Grants persistent SSH access to the host as the app user.
**Web-tier impact**
- Stored cross-site scripting on the host domain by overwriting served JavaScript (for example, Dash framework JS in `site-packages`), affecting every user on every page load until the application is restarted
- Application source code overwrite (a silent, persistent backdoor that survives normal deploys when the deploy mechanism does not fully overwrite the affected paths)
**Filesystem impact**
- Arbitrary directory creation anywhere the process can reach, via `os.makedirs()` with the unsanitized `resumableIdentifier` parameter (usable for inode exhaustion or for staging writes into nonexistent directory trees)
- Cross-user file replacement in shared upload directories, leading to data poisoning between tenants of the same application
## Affected component
- `dash_uploader/httprequesthandler.py`
- `BaseHttpRequestHandler.get_temp_root()`
- `BaseHttpRequestHandler._post()`
## Mitigation
No patch is available, and the project is archived. Options for currently-deployed users, in order of preference:
1. **Migrate to `dcc.Upload`**, the official upload component shipped with Plotly Dash. Files arrive at the callback as a base64 string; no filesystem-writing handler is exposed and no client-controlled destination path exists, so the bug class here does not apply. Best suited to small and medium files. For very large uploads, see item 2.
2. **Roll a small Flask upload handler** using `werkzeug.utils.secure_filename()` and a hardcoded server-side destination directory. Never accept client-supplied `upload_id`, filename, or identifier values as path components.
3. **If continuing to use dash-uploader**, place the upload endpoint behind authentication AND validate `upload_id`, `resumableFilename`, and `resumableIdentifier` against a strict allowlist (e.g., UUIDs only) at a layer that rewrites or rejects the request before the library handler sees it. The library's `http_request_handler` hook does NOT prevent the traversal because parameters are read from `request.form` after the hook returns; sanitization must happen above the library.
4. **At the reverse-proxy or WAF layer**, reject any request to the upload endpoint where any form field contains `..`, encoded variants (`%2e%2e`, `..%2f`, `%2e%2e%2f`), or absolute paths.
## Disclosure timeline
| Date | Event |
|---|---|
| 2026-03-17 | Vulnerability discovered during security research on a production deployment. |
| 2026-03-19 | CVE request submitted to MITRE. |
| 2026-05-07 | CVE-2026-38360 assigned by MITRE. |
| 2026-05-07 | Public advisory published. |
## Package context
- Approximately 28,000 monthly downloads on PyPI (27,756 in the 30 days preceding 2026-05-07, with sustained daily volume despite repository archival). Source: [pypistats.org](https://pypistats.org/packages/dash-uploader).
- Latest published version: `0.6.1` (stable line). Pre-releases extend to `0.7.0a2`.
- Required dependency: `dash`. Optional dependency: `pyyaml`. License: MIT.
- 11 dependent packages, 6 dependent repositories.
- 153 GitHub stars.
- Repository archived 2025-07-19 ([Issue #153](https://github.com/fohrloop/dash-uploader/issues/153)).
- No prior CVEs (verified against NVD, GitHub Advisory Database, Snyk, OSV on 2026-03-19).
## References
- https://github.com/fohrloop/dash-uploader
- https://pypi.org/project/dash-uploader/
- https://pypistats.org/packages/dash-uploader
- https://github.com/fohrloop/dash-uploader/blob/stable/dash_uploader/httprequesthandler.py
- https://github.com/fohrloop/dash-uploader/blob/dev/dash_uploader/httprequesthandler.py
- https://github.com/fohrloop/dash-uploader/issues/153
- https://cwe.mitre.org/data/definitions/22.html
## Credit
Research and disclosure by **Muhammad Fitri Bin Mohd Sultan**.