
حقن أكواد (RCE) في datamodel-code-generator عبر customBasePath غير مُتحقَّق منه (CVE-2026-63720)
الخطورة: عالية، CVSS 3.1 7.5 / CVSS 4.0 7.5 (مُعتمَد من VulnCheck، بصفتها CNA)
السقف البيئي (نشر خدمة الشبكة): حتى 9.8
المتجه (v4.0): CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
المتجه (v3.1): CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
المتأثّر: datamodel-code-generator < 0.70.0
الإصلاح في: 0.70.0
CWE: CWE-94 (تحكم غير سليم في توليد الكود، 'حقن كود')
أبلغ عنه: Rahul Karne
CNA: VulnCheck
نُشر: 26 يوليو 2026
تحقّق datamodel-code-generator من كل سلسلة استيراد يتحكم بها المخطط ويمكن أن تحمل حمولة حقن كود، باستثناء واحدة.
تحوّل الأداة مخطط الإدخال (JSON Schema أو OpenAPI أو YAML) إلى مصدر نماذج بايثون. تُكتب عدة حقول من المخطط مباشرة في ذلك الكود المُولَّد، لذلك يتحقق المشروع منها باعتبارها معرّفات بايثون منقوطة قبل الاستخدام، تحديدًا لمنع الحقن. حقل الامتداد customBasePath في المخطط هو الحقل الشقيق الوحيد الذي يتخطى هذا الفحص. تتدفق قيمته دون تعقيم إلى عبارة from ... import ... في المخرجات المُولَّدة. يمكن للمهاجم الذي يتحكم في مخطط الإدخال تضمين كود بايثون عشوائي باستخدام أسطر جديدة وتعبير خالٍ من النقاط، ويُتنفَّذ لحظة استيراد الوحدة المُولَّدة، وهي الخطوة التالية المعتادة بعد توليد النماذج.
هذا إصلاح ناقص لـ CVE-2026-55415 (GHSA-5578-w22f-pfx9)، الذي حصّن الحقلين الشقيقين customTypePath وx-python-import ضد هذه الفئة بعينها. لم يغطِّ ذلك الإصلاح customBasePath، الذي يصل إلى نفس المصب دون تحقق، وظل قابلًا للاستغلال عبر 0.68.1 والفرع الرئيسي حتى 0.70.0.
تنفيذ كود بايثون عشوائي في العملية التي تستورد النماذج المُولَّدة أو تشغّلها: جهاز المطوّر، أو عدّاء CI، أو أي خدمة تولّد النماذج ثم تحمّلها. تتعرّض سرية المضيف وسلامته وتوافره للاختراق بالكامل، محدودًا فقط بصلاحيات تلك العملية.
تعتمد الخطورة كليًا على المكان الذي يعمل فيه توليد الكود على إدخال غير موثوق:
من المتأثر: أي استخدام لـ datamodel-code-generator < 0.70.0 (1) يولّد نماذج من مخطط تكون قيمة customBasePath فيه متأثرة بالمهاجم، و(2) يستورد الوحدة المُولَّدة أو ينفذها. سير العمل الافتراضي المتمثل في التوليد ثم الاستيراد يحقق (2) بطبيعته.
من غير المتأثر:
0.70.0 أو أحدث، حيث يتم التحقق من customBasePath.| المقياس | القيمة | المصدر |
|---|---|---|
| التنزيلات، كل الوقت | 194 مليون | pepy.tech/projects/datamodel-code-generator |
| التنزيلات، آخر 30 يومًا | 16.3 مليون | pepy.tech |
| النشر النموذجي | أجهزة المطوّرين، خطوط أنابيب CI/CD، ومنصات توليد SDK التي تولّد الكود من OpenAPI / JSON Schema | متأصل في وظيفة الأداة |
تُنقل قيمة حقل المخطط customBasePath إلى الكود المُولَّد دون أي قيد على المعرّف. ثلاث نقاط في قاعدة الكود مهمة (المسارات نسبية إلى src/datamodel_code_generator/):
parser/jsonschema.py الحقل custom_base_path مع alias="customBasePath" (سطر 644 تقريبًا)، ويُستهلَك عبر _resolve_base_class(...) في عدة مواضع استدعاء.parser/base.py، في _resolve_base_class (سطر 1665 تقريبًا)، القيمة بعد normalize() محلية فقط (إزالة التكرار/القص). لا يُطبَّق أي تحقق من المعرّف.imports.py، في Import.from_full_path() (سطر 35 تقريبًا)، القيمة حرفيًا كسطر from ... import .... تُستخدم القيمة أيضًا كأساس للفئة في model/base.py في set_base_class (سطر 1324 تقريبًا) وتُعرض خامًا بواسطة قالب النموذج (class {{ class_name }}({{ base_class }}):).نظرًا لأن القيمة تُكتب في مصدر بايثون دون أي قيد، تبقى الأسطر الجديدة المضمّنة والتعبير الخالي من النقاط في المخرجات كأسطر خاصة قابلة للتحليل كلٌّ على حدة، ويُتنفَّذ السطر الأوسط عند الاستيراد.
الحمولة خالية من النقاط لضرورة. يقسم Import.from_full_path القيمة على .، لذا فإن استدعاء os.system(...) عاديًا سيتفكك. استخدام getattr(__import__('os'),'system')(...) يتجنب أي . مع حلّ الاستدعاء نفسه، وتبقي الأسطر الجديدة المحيطة أسطر from ... import ... المُصدرة صحيحة نحويًا بحيث يعمل السطر الأوسط المحقون بسلاسة.
هذا ليس مشروعًا أهمل مسألة الحقن. حصّن صاحب المشروع هذه الفئة بعينها مرارًا عبر عدة إشعارات أمنية (GHSA-5578، m34r، 8m8r، wjv6)، وفي كل مرة كان يمرر سلسلة استيراد أو نوع يتحكم بها المخطط عبر _validate_dotted_python_identifier_path قبل وصولها إلى توليد الكود. الحقلان الشقيقان customTypePath (يُتحقق منه في parser/jsonschema.py عند السطرين 4956 و5202 تقريبًا) وx-python-import (سطر 2096 تقريبًا) يمرّان عبر ذلك المدقق.
customBasePath هو الحقل الشقيق الوحيد دون مثل هذا الاستدعاء. يصل إلى نفس المصب Import.from_full_path عبر مسار مختلف (_resolve_base_class) لم يُربط أبدًا بالتحقق الذي تلقه الحقول الأخرى. نجا العيب تحديدًا لأن الدفاع المحيط بدا مكتملًا: المراجع الذي يفحص سلاسل الاستيراد غير المحققة يرى مدققين على الحقول التي يفحصها أولًا، وهذا الحقل يمر عبر مساعد يبدو وكأنه حل لفئة أساسية لا معالجة استيراد. إنها فجوة في إصلاح منهجي، لا غياب إصلاح، ولهذا استمرت حتى الإصدار الأحدث.
يحتاج المهاجم إلى:
< 0.70.0.customBasePath في مخطط سيعالجه الهدف، عمليًا بتقديم مخطط الإدخال أو التأثير فيه (مستند OpenAPI/JSON Schema تابع لطرف ثالث، أو مخطط مُرسَل إلى خدمة).لا تُطلب من المهاجم مصادقة أو صلاحيات مرتفعة (PR:N). تعكس النتيجة الأساسية أن الضحية ينفذ إجراء التوليد والاستيراد المعتاد (UI:R في v3.1 / UI:A في v4.0)؛ نشر خدمة الشبكة يزيل حتى ذلك، ومن هنا يأتي الرقم البيئي 9.8.
نُفِّذ ما يلي ضد الحزمة الحقيقية غير المعدّلة. إعادة الإنتاج:
pip install "datamodel-code-generator==0.68.1"
datamodel-codegen --input attack.json --input-file-type jsonschema --output generated_models.py
python -c "import generated_models"
إدخال المهاجم (attack.json):
{
"type": "object",
"title": "User",
"customBasePath": "builtins import object\ngetattr(__import__('os'),'system')('whoami > RCE_PROOF.txt')\nfrom builtins.object",
"properties": { "name": { "type": "string" } }
}
الملف المُولَّد generated_models.py على الإصدار الهش (0.68.1):
from __future__ import annotations
from builtins import object
getattr(__import__('os'), 'system')(
'whoami > RCE_PROOF.txt'
)
from builtins import object
class User(object):
name: str | None = None
يُصدر استدعاء المهاجم حرفيًا في المصدر المُولَّد.
عند الاستيراد: يُنفَّذ الأمر. في التشغيل الموثَّق، طُبع العلامة المحقونة إلى stdout وأُنشئ RCE_PROOF.txt يحتوي المستخدم الحالي (root)، مما يؤكد تنفيذ أوامر عشوائية عبر سير عمل التوليد والاستيراد المعتاد.
على الإصدار المصلَّح (0.70.0): يُرفض نفس المخطط قبل توليد أي كود:
Error at schema path 'attack.json': Error: customBasePath must be a dotted
Python identifier path: "builtins import object\ngetattr(__import__('os'),
'system')('whoami > RCE_PROOF.txt')\nfrom builtins.object"
لا يُنتَج أي ملف. تذكر رسالة الرفض الإصلاح مباشرة: القيمة مطلوب الآن أن تكون مسار معرّف بايثون منقوطًا.