Share
## https://sploitus.com/exploit?id=B0EABBC3-2293-5F7D-BE1D-F2CE0AAEF913
# CVE-2026-38361: Multiple Unauthenticated DoS Vulnerabilities in dash-uploader

[![CVE](https://img.shields.io/badge/CVE-2026--38361-red?style=for-the-badge)](https://vulners.com/cve/CVE-2026-38361)
[![CWE-400](https://img.shields.io/badge/CWE-400-orange?style=for-the-badge)](https://cwe.mitre.org/data/definitions/400.html)
[![CWE-670](https://img.shields.io/badge/CWE-670-orange?style=for-the-badge)](https://cwe.mitre.org/data/definitions/670.html)
[![Severity](https://img.shields.io/badge/Severity-High-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/)

Multiple unauthenticated **Denial of Service (DoS)** issues in [`fohrloop/dash-uploader`](https://github.com/fohrloop/dash-uploader) (Python, PyPI), including (but not limited to) Out-of-Memory (OOM) process crash, file truncation to zero bytes, permanent disk exhaustion, and complete bypass of the documented `max_file_size` limit. Additional resource-abuse paths exist through the same unsanitized parameter set.

### โš ๏ธ 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-38361 |
| **Vulnerability** | Uncontrolled Resource Consumption (CWE-400), Always-Incorrect Control Flow Implementation (CWE-670) |
| **CVSS 3.1** | 7.5 / High (`AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/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-38360](https://github.com/a1ohadance/CVE-2026-38360) (path traversal in same library) |

## Description

The `dash-uploader` HTTP handler accepts unauthenticated POST requests with attacker-controlled parameters that flow into memory allocation, file operations, and directory creation with no bounds checks, no rate limits, and no cleanup mechanism. Four independent issues in the same code path:

### 1. OOM crash (verified)

Verified on a 7.7 GB system: 5 concurrent POST requests with `resumableTotalChunks=30000000` triggered the Linux OOM killer within 2 seconds. Kernel log confirms:

```
Out of memory: Killed process 24203 (python3) total-vm:8302276kB, anon-rss:7068012kB
```

Each request allocates approximately 2.9 GB via a list comprehension over `range(1, resumableTotalChunks + 1)`. The server process is terminated and the application becomes completely unavailable until manual restart.

### 2. File truncation (verified)

A file containing 42 bytes of data was reduced to 0 bytes via a single POST request with `resumableTotalChunks=0`. The root cause is Python's `all()` returning `True` for empty iterables, which tricks the upload handler into treating zero chunks as a completed upload. The existing file is deleted via `os.unlink()` and replaced with an empty file.

### 3. Abandoned upload accumulation (verified)

10 orphaned temp directories with chunk files were created and persisted indefinitely on disk. A codebase-wide search across all source files for `cleanup`, `ttl`, `expire`, `garbage`, `purge`, `cron`, `schedule`, and `periodic` returned zero results. The only cleanup call (`shutil.rmtree`) executes exclusively on completed uploads. There is no mechanism to reclaim disk space from incomplete sessions.

### 4. `max_file_size` bypass (verified)

The server accepted a 5 MB chunk for a file claiming `resumableTotalSize=999999999999` (~999 GB) with HTTP 200. The `max_file_size` parameter is passed only to the React JavaScript component. The server never checks file size, chunk size, `Content-Length`, or Flask `MAX_CONTENT_LENGTH`. A developer setting `max_file_size=10` has no server-side protection.

## Vulnerable code

```python
# dash_uploader/httprequesthandler.py
def _post(self):
    resumableTotalChunks = request.form.get("resumableTotalChunks", type=int)   # attacker-controlled, no bounds
    ...
    chunk_paths = [
        os.path.join(temp_dir, get_chunk_name(resumableFilename, x))
        for x in range(1, resumableTotalChunks + 1)                              # unbounded; e.g. 30M -> ~2.9 GB -> OOM
    ]
    upload_complete = all([os.path.exists(p) for p in chunk_paths])              # all([]) is True -> truncation when chunks=0
    if upload_complete:
        target_file_name = os.path.join(temp_root, resumableFilename)
        if os.path.exists(target_file_name):
            os.unlink(target_file_name)                                          # existing file deleted
        with open(target_file_name, "ab") as target_file:
            for p in chunk_paths:                                                # empty list -> empty file written
                ...
```

Same code path produces both the OOM (large `resumableTotalChunks`) and the file-truncation primitive (`resumableTotalChunks=0`).

## Attack vectors

An attacker sends unauthenticated POST requests to the `/API/resumable` endpoint.

- **OOM crash:** 5 concurrent requests with `resumableTotalChunks=30000000` allocate ~2.9 GB each, triggering the OOM killer.
- **Disk exhaustion:** start uploads but never complete them; orphaned temp files accumulate forever.
- **File truncation:** send `resumableTotalChunks=0`; Python `all([])=True` tricks the server into overwriting the target file with empty content.
- **Size bypass:** any size limit set by the developer is enforced only in client JavaScript, so a direct HTTP request bypasses it entirely.

No authentication or privileges required.

## Impact

- Server process crash via Linux OOM killer triggered by unbounded memory allocation from a single user-controlled POST parameter (`resumableTotalChunks`)
- Permanent disk exhaustion through abandoned upload sessions that are never cleaned up (no TTL, no garbage collection, no expiry mechanism exists in the codebase)
- Filesystem inode exhaustion via arbitrary depth directory creation through `os.makedirs()` with unsanitized `resumableIdentifier`
- Data destruction via file truncation to zero bytes caused by Python `all()` returning `True` for empty iterables when `resumableTotalChunks=0`
- Bypass of all file size restrictions because `max_file_size` is enforced only in client-side JavaScript while the server-side handler performs zero size validation and never sets Flask `MAX_CONTENT_LENGTH`

## Affected components

- `dash_uploader/httprequesthandler.py` (`BaseHttpRequestHandler._post` method)
- `dash_uploader/upload.py` (`Upload` function, `max_file_size` parameter)
- `dash_uploader/configure_upload.py` (missing `MAX_CONTENT_LENGTH`)

## 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. It has no chunk-count parameter, no on-disk temporary state, and respects Flask `MAX_CONTENT_LENGTH`. None of the four issues here apply. Best suited to small and medium files. For very large uploads, see item 2.
2. **Roll a small Flask upload handler** with explicit per-request size enforcement (`MAX_CONTENT_LENGTH`), bounds on any client-supplied chunk count, and an allowlist for accepted filenames.
3. **If continuing to use dash-uploader**, set Flask `MAX_CONTENT_LENGTH` at the application level (the library does not), and reject inputs at the application or reverse-proxy layer where any of the following are true:
   - `resumableTotalChunks <= 0`
   - `resumableTotalChunks` exceeds a reasonable bound (e.g., 10,000)
   - `resumableTotalSize` exceeds the developer-configured `max_file_size`
4. **Add a rate limit** on the upload endpoint at the reverse-proxy or WAF layer to mitigate the concurrent-request OOM vector.
5. **Periodically clean up orphaned temp directories** with an external cron job, since the library has no internal cleanup.

## Disclosure timeline

| Date | Event |
|---|---|
| 2026-03-19 | Vulnerabilities discovered during security research on a production deployment. |
| 2026-03-22 | CVE request submitted to MITRE. |
| 2026-05-07 | CVE-2026-38361 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://github.com/fohrloop/dash-uploader/blob/stable/dash_uploader/httprequesthandler.py
- https://github.com/fohrloop/dash-uploader/issues/153
- https://pypi.org/project/dash-uploader/
- https://pypistats.org/packages/dash-uploader
- https://libraries.io/pypi/dash-uploader
- https://pepy.tech/project/dash-uploader
- https://cwe.mitre.org/data/definitions/400.html
- https://cwe.mitre.org/data/definitions/670.html
- https://docs.python.org/3/library/functions.html#all

## Discoverer

Muhammad Fitri Bin Mohd Sultan