a1ohadance/CVE-2026-38361

GitHub: a1ohadance/CVE-2026-38361

针对已归档的 Python 库 dash-uploader 中多个未经身份验证的拒绝服务漏洞(CVE-2026-38361)的公开安全公告,详述了 OOM 崩溃、文件截断、磁盘耗尽和大小限制绕过四种攻击方式及其缓解措施。

Stars: 0 | Forks: 0

# CVE-2026-38361:dash-uploader 中多个未经身份验证的 DoS 漏洞 [![CVE](https://img.shields.io/badge/CVE-2026--38361-red?style=for-the-badge)](https://www.cve.org/CVERecord?id=CVE-2026-38361) [![NVD](https://img.shields.io/badge/NVD-CVE--2026--38361-blue?style=for-the-badge)](https://nvd.nist.gov/vuln/detail/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) [![严重性](https://img.shields.io/badge/Severity-High-red?style=for-the-badge)](#) [![补丁](https://img.shields.io/badge/Patch-None-black?style=for-the-badge)](#缓解措施) [![身份验证](https://img.shields.io/badge/Auth-None_required-red?style=for-the-badge)](#攻击向量) [![版本](https://img.shields.io/badge/dash--uploader-0.6.1-blue?style=for-the-badge)](https://pypi.org/project/dash-uploader/) [![PyPI 下载量](https://img.shields.io/badge/PyPI%20downloads-28K%2Fmonth-blue?style=for-the-badge)](https://pepy.tech/project/dash-uploader) [![总下载量](https://img.shields.io/badge/Total%20downloads-733.08K-blue?style=for-the-badge)](https://pepy.tech/project/dash-uploader) [![许可证](https://img.shields.io/pypi/l/dash-uploader?style=for-the-badge)](https://pypi.org/project/dash-uploader/) [`fohrloop/dash-uploader`](https://github.com/fohrloop/dash-uploader) (Python, PyPI) 中存在多个未经身份验证的**拒绝服务** 问题,包括(但不限于)内存溢出 (OOM) 进程崩溃、文件截断为零字节、永久性磁盘耗尽,以及完全绕过文档中所描述的 `max_file_size` 限制。通过同一组未经过滤的参数,还存在其他资源滥用路径。 ### ⚠️ 没有可用的补丁,并且将来也永远不会发布 该代码库已于 [2025-07-19 被归档](https://github.com/fohrloop/dash-uploader/issues/153),目前没有活跃的维护者。所有已发布的版本(从 `0.1.0` 到 `0.7.0a2`)均受影响,并将持续如此。该软件包每月仍有约 28,000 次下载。 任何在生产环境中运行 `dash-uploader` 的用户都必须自行采取缓解措施。推荐的修复方案是迁移到 Plotly Dash 内置的 `dcc.Upload` 组件。有关完整的解决方案,请参阅[缓解措施](#mitigation)。 | | | |---|---| | **CVE ID** | [CVE-2026-38361](https://www.cve.org/CVERecord?id=CVE-2026-38361) ([NVD](https://nvd.nist.gov/vuln/detail/CVE-2026-38361)) | | **漏洞类型** | 不受控制的资源消耗 (CWE-400),始终不正确的控制流实现 (CWE-670) | | **CVSS 3.1** | 7.5 / 高危 (`AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H`) | | **产品** | dash-uploader | | **受影响版本** | `0.1.0` 到 `0.7.0a2`(所有 18 个发行版) | | **修复版本** | 无(项目已于 2025-07-19 归档) | | **攻击向量** | 远程,无需身份验证 | | **发现者** | Muhammad Fitri Bin Mohd Sultan | | **分配方** | MITRE, 2026-05-07 | | **相关** | [CVE-2026-38360](https://github.com/a1ohadance/CVE-2026-38360)(同一库中的路径遍历漏洞) | ## 漏洞描述 `dash-uploader` HTTP 处理程序接受未经身份验证的 POST 请求,其中包含攻击者控制的参数,这些参数会直接流入内存分配、文件操作和目录创建过程,期间没有边界检查、速率限制或清理机制。同一段代码执行路径中存在四个独立的问题: ### 1. OOM 崩溃(已验证) 在一台 7.7 GB 内存系统上验证:5 个并发 POST 请求(带有 `resumableTotalChunks=30000000`)在 2 秒内触发了 Linux OOM killer。内核日志确认如下: ``` Out of memory: Killed process 24203 (python3) total-vm:8302276kB, anon-rss:7068012kB ``` 每个请求通过针对 `range(1, resumableTotalChunks + 1)` 的列表推导式分配了大约 2.9 GB 内存。服务进程被终止,应用程序完全不可用,直到手动重启。 ### 2. 文件截断(已验证) 一个包含 42 字节数据的文件,通过一个带有 `resumableTotalChunks=0` 的 POST 请求被缩减为 0 字节。根本原因是 Python 的 `all()` 对空可迭代对象会返回 `True`,这诱使上传处理程序将零分块视为上传完成。现有文件通过 `os.unlink()` 被删除并替换为空文件。 ### 3. 废弃上传文件累积(已验证) 创建了 10 个包含分块文件的孤立临时目录,并在磁盘上无限期保留。在所有源文件的代码库范围内搜索 `cleanup`、`ttl`、`expire`、`garbage`、`purge`、`cron`、`schedule` 和 `periodic`,均未返回任何结果。唯一的清理调用 (`shutil.rmtree`) 仅在上传完成时执行。没有任何机制可以回收未完成会话所占用的磁盘空间。 ### 4. 绕过 `max_file_size`(已验证) 服务器接受了一个 5 MB 的分块上传请求,而该文件声称的 `resumableTotalSize=999999999999`(约 999 GB),并返回 HTTP 200。`max_file_size` 参数仅传递给 React JavaScript 组件。服务器从不检查文件大小、分块大小、`Content-Length` 或 Flask 的 `MAX_CONTENT_LENGTH`。开发者设置的 `max_file_size=10` 没有任何服务器端的保护。 ## 漏洞代码 ``` # 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 ... ``` 同一段代码路径既导致了 OOM(巨大的 `resumableTotalChunks`),又导致了文件截断原语(`resumableTotalChunks=0`)。 ## 攻击向量 攻击者向 `/API/resumable` 端点发送未经身份验证的 POST 请求。 - **OOM 崩溃:** 5 个带有 `resumableTotalChunks=30000000` 的并发请求,每个分配约 2.9 GB 内存,从而触发 OOM killer。 - **磁盘耗尽:** 启动上传但永不完成;孤立的临时文件会永远累积。 - **文件截断:** 发送 `resumableTotalChunks=0`;Python `all([])=True` 诱使服务器用空内容覆盖目标文件。 - **大小限制绕过:** 开发者设置的任何大小限制仅在客户端 JavaScript 中强制执行,因此直接发送 HTTP 请求即可完全绕过它。 无需任何身份验证或权限。 ## 影响范围 - 由单个用户控制的 POST 参数 (`resumableTotalChunks`) 引发无限制的内存分配,进而触发 Linux OOM killer,导致服务进程崩溃 - 由于未完成的上传会话永不清理(代码库中不存在 TTL、垃圾回收或过期机制),从而导致永久性磁盘耗尽 - 通过未经过滤的 `resumableIdentifier` 使用 `os.makedirs()` 创建任意深度的目录,导致文件系统 inode 耗尽 - 当 `resumableTotalChunks=0` 时,由于 Python `all()` 对空可迭代对象返回 `True`,导致文件被截断为零字节,进而造成数据破坏 - 绕过所有文件大小限制,因为 `max_file_size` 仅在客户端 JavaScript 中强制执行,而服务器端处理程序执行零次大小验证,并且从不设置 Flask `MAX_CONTENT_LENGTH` ## 受影响组件 - `dash_uploader/httprequesthandler.py`(`BaseHttpRequestHandler._post` 方法) - `dash_uploader/upload.py`(`Upload` 函数,`max_file_size` 参数) - `dash_uploader/configure_upload.py`(缺失 `MAX_CONTENT_LENGTH`) ## 缓解措施 ### ⚠️ 无可用补丁,且项目已归档 针对当前已部署用户的解决方案,按优先顺序排列: 1. **迁移到 `dcc.Upload`**,这是 Plotly Dash 官方提供的上传组件。它没有分块计数参数,没有磁盘上的临时状态,并且遵循 Flask `MAX_CONTENT_LENGTH`。此处提到的四个问题均不适用于它。最适合中小型文件。对于超大文件的上传,请参见第 2 项。 2. **自行编写一个小型的 Flask 上传处理程序**,包含明确的按请求大小限制(`MAX_CONTENT_LENGTH`)、对客户端提供的任何分块数量的边界限制,以及针对可接受文件名的白名单。 3. **如果继续使用 dash-uploader**,请在应用程序层面设置 Flask `MAX_CONTENT_LENGTH`(该库并未设置),并在应用程序层或反向代理层拒绝满足以下任一条件的输入: - `resumableTotalChunks <= 0` - `resumableTotalChunks` 超过合理界限(例如 10,000) - `resumableTotalSize` 超过开发者配置的 `max_file_size` 4. **添加速率限制**,在反向代理或 WAF 层对上传端点进行限制,以缓解并发请求导致的 OOM 攻击向量。 5. **定期清理孤立的临时目录**,通过外部 cron 作业执行,因为该库没有内部清理机制。 ## 披露时间线 | 日期 | 事件 | |---|---| | 2026-03-19 | 在对生产部署进行安全研究期间发现了漏洞。 | | 2026-03-22 | 向 MITRE 提交了 CVE 申请。 | | 2026-05-07 | CVE-2026-38361 由 MITRE 分配。 | | 2026-05-07 | 发布公开安全公告。 | | 2026-05-09 | CVE 记录在 [MITRE CVE 数据库](https://www.cve.org/CVERecord?id=CVE-2026-38361) 和 [NVD](https://nvd.nist.gov/vuln/detail/CVE-2026-38361) 上发布。 | ## 包上下文 - PyPI 上每月下载量约为 28,000 次(在 2026-05-07 之前的 30 天内为 27,756 次,尽管代码库已归档,但每日下载量仍保持稳定)。来源:[pypistats.org](https://pypistats.org/packages/dash-uploader)。 - 最新发布版本:`0.6.1`(稳定版线)。预发布版本延伸至 `0.7.0a2`。 - 必需依赖项:`dash`。可选依赖项:`pyyaml`。许可证:MIT。 - 11 个依赖包,6 个依赖代码库。 - 153 个 GitHub Stars。 - 代码库已于 2025-07-19 归档([Issue #153](https://github.com/fohrloop/dash-uploader/issues/153))。 - 无先前的 CVE 记录(已于 2026-03-19 对照 NVD、GitHub Advisory Database、Snyk 和 OSV 进行了验证)。 ## 参考资料 - https://www.cve.org/CVERecord?id=CVE-2026-38361 - https://nvd.nist.gov/vuln/detail/CVE-2026-38361 - 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 ## 发现者 Muhammad Fitri Bin Mohd Sultan
标签:CVE-2026-38361, CWE-400, CWE-670, Dash, dash-uploader, DoS, OOM, PyPI, Python, Web安全, 内存耗尽, 开源组件漏洞, 拒绝服务攻击, 文件上传漏洞, 无后门, 未授权攻击, 漏洞分析, 漏洞通报, 磁盘耗尽, 网络安全, 蓝队分析, 资源消耗攻击, 路径探测, 软件供应链安全, 远程方法调用, 逆向工具, 配置错误, 隐私保护, 零日漏洞, 高危漏洞