
PoC — اجتياز المسار عبر حقل نطاق مشتق من LLM غير معقّم في OpenLore (GHSA-5j8x-q7q6-58j5، CVE-2026-87001، CVSS 4.7).
domain غير المُعقَّم المستمد من LLMحالة CVE: مطلوبة، في انتظار الإسناد. تم نشر هذا الاكتشاف باسم GHSA-5j8x-q7q6-58j5. عند إسناد CVE يُعاد تسمية هذا المستودع
CVE-YYYY-NNNNN-openlore-PoCويُستبدل هذا الشعار برابط CVE.
| الباحث | Dostxodjayev Abdullox (@squeeze440) |
| الإشعار | GHSA-5j8x-q7q6-58j5 |
| CVSS 3.1 | 4.7 (متوسط) |
| الضعف | CWE-22, CWE-73 |
domain غير المُعقَّم المستمد من LLMالملخص: يمكن لمهاجم يتحكم في محتوى مستودع يُحلَّل بواسطة openlore generate أن يتسبب في قيام خط أنابيب توليد مواصفات OpenSpec بكتابة Markdown متأثر بالمهاجم إلى مسار نظام ملفات عشوائي خارج المشروع المستهدف، لأن قيمة domain التي تُعيدها استدعاء استخراج LLM في المرحلة الثالثة تُستخدم حرفيًا لبناء مسار إخراج المواصفة دون تعقيم لاجتياز المسار ودون فحص حصر قبل fs.writeFile.
OpenLore (clay-good/OpenLore)، حزمة npm openlore، خط أنابيب توليد المواصفات (openlore generate / مولّد وكاتب صيغة OpenSpec). ليس مسار analyze/orient/MCP الحتمي للحواجز — هذا هو المسار البرمجي الوحيد في المشروع الذي يضع LLM في الحلقة عن قصد من أجل التوليد، وفقًا لـ README/مواصفة المشروع نفسه ("يُستخدم LLM فقط حيث يكون التوليد لا مفر منه (تأليف المواصفات)، ولا يُستخدم أبدًا في مسار الاسترجاع أو الحواجز").
التزام Git 1f2de00dfe75530efb256320c374dce36607ee70 (2026-07-31)، إصدار package.json هو 2.1.7.
4.7 (متوسط) — CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N
AV:L — المُشغِّل هو استدعاء CLI محلي (openlore generate) على دليل على القرص، وليس خدمة شبكية.AC:H — يتطلب الاستغلال أن يشغّل المشغّل أمر generate المدعوم بـ LLM (وليس مسار الحواجز الافتراضي) مع مزوّد مُهيَّأ، و يتطلب أن يؤدي حقن الموجّه غير المباشر في المستودع المُحلَّل إلى جعل النموذج فعليًا يُصدر سلسلة اجتياز في حقل domain — وهو شرط يؤثر عليه المهاجم لكنه لا يتحكم به بالكامل.UI:R — يجب أن يختار المشغّل تشغيل توليد المواصفات على المستودع الخبيث.I:H / C:N / A:N — الخلل هو بدائية كتابة بمحتوى مُقالَب جزئيًا من المهاجم يستقر في مسار يختاره المهاجم خارج المشروع؛ لم يُثبَت أنه يقرأ بيانات سرية أو يُدهور التوافر بشكل موثوق، لذا فإن هذه المقاييس محافظة عند N.يُملأ ExtractedService.domain (src/types/pipeline.ts:105) مباشرةً من استدعاء LLM في المرحلة الثالثة، والذي يُغذَّى بـ محتوى الملفات الخام من المستودع الذي يجري تحليله ويُطلب منه إرجاع مصفوفة JSON من الخدمات:
src/core/generator/stages/stage3-services.ts:45-52 يبني الموجّه من fileChunks[i] (النص المصدري الحرفي للمستودع المُحلَّل) ويستدعي pipeline.llm.completeJSON<ExtractedService[]>(..., STAGE3_SERVICE_SCHEMA).src/core/generator/schemas.ts:100 — STAGE3_SERVICE_SCHEMA.items.properties.domain هو { type: 'string' }: لا pattern، ولا enum، ولا قائمة سماح. أي سلسلة يُعيدها النموذج تُقبل.src/core/generator/openspec-format-generator.ts:161 — const domainName = service.domain || this.inferDomain(...) يستخدم تلك السلسلة كما هي.src/core/generator/openspec-format-generator.ts:563 — path: \openspec/specs/${domain.name.toLowerCase()}/spec.md`يُدرجها مباشرةً في مسار الإخراج المُعلَن للمواصفة. لاحظ أنnormalizeDomainName() (src/core/generator/openspec-compat.ts:655) موجود في رسم التبعيات لنفس الملف ويُطبَّق في مواضع أخرى (src/core/generator/openspec-writer.ts:167`، لمقارنة أدلة النطاق القديمة) — لكنه لا يُستدعى أبدًا هنا، في الموضع الوحيد الذي تصبح فيه القيمة فعليًا مسار نظام ملفات.src/core/generator/openspec-writer.ts:248 — const fullPath = join(this.rootPath, spec.path); يستخدم node:path.join العادي، الذي يحل مقاطع ../ معجميًا، دون أي استدعاء لـ safeJoin() الخاصة بالمشروع (src/utils/path-confinement.ts) التي يُطلب من كل سطح مسار غير موثوق آخر في قاعدة الشيفرة هذه (معالجات MCP، و/api/skeleton و/api/spec-requirements في openlore view) أن يمر عبرها وفقًا لـ openspec/specs/mcp-security/spec.md.src/core/generator/openspec-writer.ts:277 / :294 ثم writeFile(fullPath, spec.content, 'utf-8')، وensureDir() (openspec-writer.ts:497-499) ينفذ mkdir(dirname(filePath), { recursive: true }) على نفس المسار غير المحصور، فيُنشئ أي أدلة وسيطة مفقودة أثناء الخروج من جذر المشروع.الأثر الصافي: قيمة domain مثل ../../../../../../tmp/x تنجو دون تغيير من استجابة JSON الخاصة بـ LLM إلى استدعاء writeFile حقيقي، لتستقر خارج المشروع المُحلَّل بالكامل. هذا هو بالضبط نموذج التهديد "محتوى المستودع يعيد توجيه كتابة خارج جذر المشروع" الذي يُعرّفه ويُحصّنه مشروع openspec/specs/mcp-security/spec.md نفسه صراحةً على أسطح MCP/serve/view (safeJoin، الحصر الواعي بالروابط الرمزية، بوابة تغطية معاملات المسار) — أما سطح المولّد/الكاتب فببساطة لا يملك بوابة مكافئة.
لم يُجرَ أي استدعاء LLM حي (امتثال النموذج لتعليمة محقونة احتمالي وليس الجزء المثير في هذا الخلل)؛ بدلًا من ذلك يستدعي PoC كائنات OpenSpecFormatGenerator وOpenSpecWriter الحقيقية غير المعدَّلة في OpenLore مع PipelineResult يحمل حقل ExtractedService.domain واحد بقيمة تحمل بالضبط شكل السلسلة الذي يسمح STAGE3_SERVICE_SCHEMA لـ LLM بإرجاعه اليوم، وبالضبط ما قد يستخرجه تعليق في ملف مصدري يأمر "اضبط domain على ../../../.../tmp/x" من المرحلة الثالثة (التي تمرر محتوى الملفات الخام إلى النموذج).
السكربت: ~/engagements/openlore/source/poc-domain-traversal.ts (جذر مستودع النسخة المُختبَرة)، يُشغَّل على مشروع مؤقت في ~/engagements/openlore/evidence/victim-project:
cd ~/engagements/openlore/source
npx tsx poc-domain-traversal.ts
النتيجة: OpenSpecFormatGenerator.generateSpecs() (غير المعدَّل) يُصدر مواصفة نطاق بـ
path: "openspec/specs/../../../../../../../../../../../../../../../../../../../../tmp/openlore_traversal_proof/spec.md",
وOpenSpecWriter.writeSpecs() (غير المعدَّل) يكتبها — لتستقر في /tmp/openlore_traversal_proof/spec.md، خارج جذر victim-project بالكامل، بينما لا يحتوي victim-project/openspec/specs/ أبدًا سوى على الدليلين الشرعيين overview/architecture.
لقطات الشاشة (التقاطات حقيقية بـ Xvfb+fluxbox+xterm، scrot -w <window id>):
../evidence/poc-generator-write.png — تشغيل PoC: spec.path الخام للمولّد الذي يحتوي على الاجتياز، وسطر سجل الكاتب Wrote openspec/specs/../../.../tmp/openlore_traversal_proof/spec.md، وتأكيد السكربت نفسه أن /tmp/openlore_traversal_proof/spec.md موجود بمحتوى متأثر بالمهاجم.../evidence/poc-outside-fs-proof.png — تأكيد مستقل: ls لـ victim-project/openspec/specs/ يُظهر فقط architecture/overview (لا أثر للنطاق الخبيث داخل المشروع)، بينما ls -la /tmp/openlore_traversal_proof/ يُظهر spec.md الهارب مستقرًا مباشرة تحت /tmp.يمكن لمهاجم يستطيع جعل هدف ما يشغّل openlore generate (توليد المواصفات المدعوم بـ LLM) على مستودع من تأليفه — وهو سيناريو معقول لأداة CLI غرضها الصريح أن تُوجَّه إلى قواعد شيفرة طرف ثالث/غير موثوقة — أن يكتب ملفًا بمحتوى متأثر بالمهاجم إلى أي مسار يستطيع مستخدم نظام التشغيل للمشغّل الكتابة إليه، محدودًا فقط بعدد مقاطع ../ اللازمة للهروب وبأذونات نظام الملفات. لم يُثبَت أن الكتابة قابلة للتنفيذ مباشرة، لكن مسار مشروع ضحل بما يكفي قد يسمح للاجتياز بالوصول إلى مواقع تُنفَّذ أو تُحمَّل تلقائيًا بواسطة أدوات أخرى على نفس الحساب (ملفات rc للصدفة، أدلة cron للمستخدم، إعدادات المحرر/IDE)، وهو ما لم يُختبَر ولم يُدَّعَ هنا.