Langflow has Unauthenticated Remote Code Execution via Public Flow Build Endpoint
Corrija agora. Ela está sob exploração confirmada pelo CISA e tem exploit funcional público.
Apply mitigations per vendor instructions, follow applicable BOD 22-01 guidance for cloud services, or discontinue use of the product if mitigations are unavailable.
Resumo
Falha crítica no Langflow que permite execução remota de código sem autenticação através do endpoint público de build de flows (POST /api/v1/build_public_tmp/{flow_id}/flow). O endpoint foi desenhado para ser acessível sem login (para flows marcados como públicos), mas aceitava um parâmetro opcional 'data' que substituía a definição do flow armazenada no banco por dados arbitrários enviados pelo atacante — incluindo código Python executado sem qualquer sandboxing. O impacto é RCE completo na máquina que roda o Langflow, e já há exploração confirmada em massa (CISA KEV, template Nuclei público, PoC funcional).
Detalhamento técnico
A causa raiz é um CWE-94 (Code Injection) combinado com falta de controle de acesso (CWE-306) no endpoint build_public_tmp, em src/backend/base/langflow/api/v1/chat.py. Diferente do endpoint autenticado de build (que exige CurrentActiveUser), este endpoint não tem nenhuma dependência de autenticação — por design, para permitir que flows públicos sejam executados por qualquer visitante. O problema é que ele aceitava um corpo JSON com o parâmetro 'data' (do tipo FlowDataRequest), e quando presente, esse 'data' era passado diretamente para start_flow_build() no lugar da definição do flow persistida no banco.
A partir daí, a cadeia de execução segue: start_flow_build(data=attacker_data) → build_graph_from_data() → Graph.from_payload() → _instantiate_components_in_vertices() → instantiate_class() em loading.py, que extrai o campo 'code' de cada nó e chama eval_custom_component_code(). Essa função vai até create_class() em eval.py e finalmente até prepare_global_scope() em src/lfx/src/lfx/custom/validate.py (linha ~397), onde o código do componente é compilado e passado para exec(compiled_code, exec_globals) sem nenhuma restrição de namespace, módulos importáveis ou chamadas de sistema.
Um detalhe técnico relevante, levantado pelo próprio advisory: prepare_global_scope executa qualquer nó ast.Assign no nível superior do módulo do componente. Isso significa que uma atribuição simples como '_x = os.system("id")' já é executada durante a fase de construção do grafo — antes mesmo de o flow ser efetivamente 'executado'. Não é preciso sequer que o componente rode como parte do pipeline; a instanciação do componente malicioso já dispara o exec().
É uma classe distinta da CVE-2025-3248, que atingia o endpoint /api/v1/validate/code e foi corrigida adicionando autenticação. Aqui a falha está num endpoint que é deliberadamente público — o erro foi permitir que um parâmetro que deveria ser ignorado (a definição do flow, que só o dono deveria poder alterar via API autenticada) fosse aceito e usado no lugar dos dados confiáveis do banco.
Como é explorada
Pré-requisito real: o atacante precisa apenas conhecer o UUID de um flow marcado como PUBLIC na instância-alvo. Não precisa de credenciais, chave de API nem cookie de sessão válido — o endpoint só lê um cookie 'client_id' opcional, que pode ser qualquer string arbitrária. Flows públicos são comuns em demos, chatbots embutidos em sites e workflows compartilhados via link, então a superfície de exposição é maior do que a manchete 'unauthenticated RCE' sozinha sugere — instâncias sem nenhum flow público não são exploráveis por esta rota.
Na prática documentada em PoC, o atacante envia um POST para /api/v1/build_public_tmp/{flow_id}/flow com um corpo JSON contendo 'data.nodes' onde um nó de componente customizado tem, no campo 'code', um script Python arbitrário (import de módulos do sistema, chamadas via os.popen/os.system, escrita em disco, abertura de socket, etc.). O servidor compila e executa esse código no contexto do processo Langflow, com os mesmos privilégios do usuário do serviço. O advisory publicado documenta reprodução confirmada (6/6 execuções) contra a versão 1.7.3 instalada via pip.
Um agravante citado no advisory: quando AUTO_LOGIN=true (configuração padrão em muitas instalações), um atacante não autenticado pode ainda obter um token de superusuário via /api/v1/auto_login e criar seu próprio flow público antes de disparar o exploit — eliminando até a necessidade de descobrir um flow público pré-existente. A vulnerabilidade está no catálogo KEV da CISA, o que confirma exploração ativa observada em ambiente real, e existe template Nuclei público, o que baixa a barreira técnica para varreduras automatizadas em massa.
Versões
Como se proteger
Atualizar para Langflow 1.9.0 ou superior, versão em que o parâmetro 'data' foi removido da assinatura do endpoint build_public_tmp — o commit de correção (73b6612) faz o endpoint sempre carregar a definição do flow do banco de dados, ignorando qualquer 'data' externo. Não existe backport documentado para ramos anteriores a 1.9.0; quem não pode atualizar imediatamente deve tratar a instância como comprometível.
Como controle compensatório caso a atualização não seja imediata: remover ou despublicar todos os flows marcados como PUBLIC (access_type=PUBLIC) reduz drasticamente a superfície, já que a exploração depende de existir ao menos um flow público. Também vale desativar AUTO_LOGIN em produção — isso não corrige a falha em si, mas elimina o caminho onde um atacante sem qualquer credencial cria seu próprio flow público para viabilizar o ataque. Bloquear ou filtrar no perímetro (proxy/WAF) requisições POST para /api/v1/build_public_tmp/*/flow que contenham o campo 'data' no corpo é uma mitigação técnica direta enquanto a atualização não é aplicada, já que o endpoint legítimo, pós-correção, nunca precisa desse campo.
Não funciona como mitigação: apenas restringir a criação de novos flows públicos via política de uso, sem controle técnico — não impede exploração de flows públicos já existentes nem de instâncias com AUTO_LOGIN ativo. Também não basta ter corrigido a CVE-2025-3248 (autenticação em /api/v1/validate/code): são endpoints e código-caminho diferentes, e uma instância pode estar protegida contra aquela e ainda assim vulnerável a esta.
Como detectar
Nos logs de acesso ao servidor web/API, procurar por requisições POST para /api/v1/build_public_tmp/{flow_id}/flow cujo corpo contenha o campo 'data' com estrutura 'nodes' incluindo um campo 'template.code.value' com código Python — especialmente presença de padrões como 'import os', 'os.system', 'os.popen', 'socket', ou nomes de classe/componente genéricos e atípicos (ex.: 'ExploitComp', IDs de nó como 'Exploit-001' usados no PoC público, embora atacantes reais provavelmente variem esses nomes). Como o Nuclei já tem template público para esta CVE, sinais de varredura automatizada em massa (múltiplas tentativas contra UUIDs de flow sequenciais ou aleatórios, vindas de poucos IPs, em curto espaço de tempo) também são indicativos.
No host, verificar processos filhos inesperados do processo Langflow (python executando os.system/os.popen), conexões de saída não usuais originadas do serviço, e arquivos criados fora do diretório esperado da aplicação. Se o proxy reverso ou API gateway não registra o corpo das requisições POST, não há sinal confiável no log de acesso padrão — esse é um ponto relevante: ambientes sem logging de payload em endpoints de API não conseguirão detectar retroativamente a exploração por esta via específica.