
Advisory: CVE-2026-38361 multiple DoS vulnerabilities (CWE-400/CWE-670) in dash-uploader (Python/PyPI)
Множественные проблемы «Отказа в обслуживании» (DoS) без аутентификации в fohrloop/dash-uploader (Python, PyPI), включая (но не ограничиваясь) крах процесса из-за нехватки памяти (OOM), усечение файла до нулевого размера, постоянное исчерпание дискового пространства и полный обход документированного ограничения max_file_size. Дополнительные пути злоупотребления ресурсами существуют через тот же несанированный набор параметров.
Репозиторий был заархивирован 2025-07-19 без активного мейнтейнера. Все опубликованные версии (от 0.1.0 до 0.7.0a2) затронуты и останутся таковыми. Пакет всё ещё набирает примерно 28 000 загрузок в месяц.
Любой, кто использует dash-uploader в продакшене, должен применить собственные меры смягчения. Рекомендуемое исправление — перейти на встроенный компонент dcc.Upload из Plotly Dash. Полные варианты см. в разделе Смягчение.
HTTP-обработчик dash-uploader принимает неаутентифицированные POST-запросы с параметрами, контролируемыми атакующим, которые затем используются при выделении памяти, файловых операциях и создании каталогов без проверки границ, ограничения скорости или механизма очистки. Четыре независимые проблемы в одном и том же участке кода:
Подтверждено на системе с 7,7 ГБ: 5 одновременных POST-запросов с resumableTotalChunks=30000000 вызвали OOM-killer за 2 секунды. Лог ядра подтверждает:
Out of memory: Killed process 24203 (python3) total-vm:8302276kB, anon-rss:7068012kB
Каждый запрос выделяет примерно 2.9 ГБ через списковое включение по range(1, resumableTotalChunks + 1). Серверный процесс завершается, и приложение становится полностью недоступным до ручного перезапуска.
Файл размером 42 байта был уменьшен до 0 байт с помощью одного POST-запроса с resumableTotalChunks=0. Основная причина — all() в Python возвращает True для пустых итераций, что обманывает обработчик загрузки, заставляя его считать нулевое количество частей завершённой загрузкой. Существующий файл удаляется через os.unlink() и заменяется пустым файлом.
10 потерянных временных каталогов с файлами частей было создано и сохранялось на диске бессрочно. Поиск по всем исходным файлам на предмет cleanup, ttl, expire, garbage, purge, cron, schedule и periodic не дал результатов. Единственный вызов очистки (shutil.rmtree) выполняется исключительно при завершённых загрузках. Механизма освобождения дискового пространства от незавершённых сессий нет.
max_file_size (подтверждено)Сервер принял часть размером 5 МБ для файла с заявленным resumableTotalSize=999999999999 (~999 ГБ) с 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).
Атакующий отправляет неаутентифицированные POST-запросы на конечную точку /API/resumable.
resumableTotalChunks=30000000 выделяют ~2.9 ГБ каждый, вызывая OOM-killer.resumableTotalChunks=0; Python all([])=True заставляет сервер перезаписать целевой файл пустым содержимым.Аутентификация или привилегии не требуются.
resumableTotalChunks)os.makedirs() с несанированным resumableIdentifierall() возвращает True для пустых итераторов при resumableTotalChunks=0max_file_size применяется только в клиентском JavaScript, а серверный обработчик не выполняет никакой проверки размера и никогда не устанавливает Flask MAX_CONTENT_LENGTHdash_uploader/httprequesthandler.py (метод BaseHttpRequestHandler._post)dash_uploader/upload.py (функция Upload, параметр max_file_size)dash_uploader/configure_upload.py (отсутствует MAX_CONTENT_LENGTH)Варианты для тех, кто уже использует пакет, в порядке предпочтения:
dcc.Upload — официальный компонент загрузки, поставляемый с Plotly Dash. У него нет параметра количества частей, нет временного состояния на диске, и он уважает Flask MAX_CONTENT_LENGTH. Ни одна из четырёх проблем здесь не применима. Лучше всего подходит для небольших и средних файлов. Для очень больших загрузок см. пункт 2.MAX_CONTENT_LENGTH), ограничениями на любое предоставленное клиентом количество частей и белым списком допустимых имён файлов.MAX_CONTENT_LENGTH на уровне приложения (библиотека этого не делает) и отклоняйте входные данные на уровне приложения или обратного прокси, если выполняется любое из следующих условий:
resumableTotalChunks <= 0resumableTotalChunks превышает разумный предел (например, 10 000)resumableTotalSize превышает настроенный разработчиком max_file_size0.6.1 (стабильная линия). Пре-релизы до 0.7.0a2.dash. Необязательная зависимость: pyyaml. Лицензия: MIT.Muhammad Fitri Bin Mohd Sultan
| CVE ID | CVE-2026-38361 (NVD) |
| Уязвимость | Неконтролируемое потребление ресурсов (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 (обход пути в той же библиотеке) |
| Дата | Событие |
|---|
| 2026-03-19 | Уязвимости обнаружены в ходе исследования безопасности продакшн-развёртывания. |
| 2026-03-22 | Запрос CVE отправлен в MITRE. |
| 2026-05-07 | CVE-2026-38361 назначен MITRE. |
| 2026-05-07 | Опубликовано публичное уведомление. |
| 2026-05-09 | Запись CVE опубликована в базе данных CVE MITRE и в NVD. |