PACKETSTORM

📄 Dash-Uploader 0.7.0a2 Path Traversal_PACKETSTORM:220639

Description

There is an unauthenticated path traversal in dash-uploader versions 0.1.0 through 0.7.0a2 allowing arbitrary file write, leading to but not limited to remote code execution, application source code overwrite, stored cross site scripting, and...
Visit Original Source

Basic Information

ID PACKETSTORM:220639
Published May 8, 2026 at 00:00

Affected Product

Affected Versions # CVE-2026-38360: Path Traversal in dash-uploader

[![CVE](https://img.shields.io/badge/CVE-2026--38360-red?style=for-the-badge)](https://www.cve.org/CVERecord?id=CVE-2026-38360)
[![CWE](https://img.shields.io/badge/CWE-22-orange?style=for-the-badge)](https://cwe.mitre.org/data/definitions/22.html)
[![Severity](https://img.shields.io/badge/Severity-Critical-red?style=for-the-badge)](#)
[![Patch](https://img.shields.io/badge/Patch-None-black?style=for-the-badge)](#mitigation)
[![Auth](https://img.shields.io/badge/Auth-None_required-red?style=for-the-badge)](#attack-vectors)
[![Version](https://img.shields.io/badge/dash--uploader-0.6.1-blue?style=for-the-badge)](https://pypi.org/project/dash-uploader/)
[![PyPI Downloads](https://img.shields.io/badge/PyPI%20downloads-28K%2Fmonth-blue?style=for-the-badge)](https://pepy.tech/project/dash-uploader)
[![Total Downloads](https://img.shields.io/badge/Total%20downloads-733.08K-blue?style=for-the-badge)](https://pepy.tech/project/dash-uploader)
[![License](https://img.shields.io/pypi/l/dash-uploader?style=for-the-badge)](https://pypi.org/project/dash-uploader/)

Unauthenticated path 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.

### ⚠️ 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** | Path Traversal (CWE-22) |
| **CVSS 3.1** | 9.8 / Critical (`AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H`) |
| **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 |
| **Related** | [CVE-2026-38361](https://github.com/a1ohadance/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 path traversal from an authenticated user.

## Vulnerable code

```python
# dash_uploader/httprequesthandler.py
def _post(self):
resumableFilename = request.form.get("resumableFilename", default="error", type=str)
resumableIdentifier = request.form.get("resumableIdentifier", default="error", type=str)
upload_id = request.form.get("upload_id", default="", type=str)
...
temp_root = self.get_temp_root(upload_id) # upload_id flows in here
temp_dir = os.path.join(temp_root, resumableIdentifier) # raw user input -> os.path.join
if not os.path.isdir(temp_dir):
os.makedirs(temp_dir) # raw user input -> os.makedirs

def get_temp_root(self, upload_id):
return os.path.join(self.upload_folder, upload_id) # no sanitization: ../../ escapes upload_folder
```

Three independent traversal sinks share the same root cause: form values reach `os.path.join` and `os.makedirs` with no validation.

## Attack vectors

An unauthenticated remote attacker sends an HTTP POST multipart request to the upload endpoint. By injecting path 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

## Discoverer

Muhammad Fitri Bin Mohd Sultan

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.