
اعتمد نظام Plane’s V2 asset subsystem على workspace slugs و asset UUIDs دون فرض فحوصات العضوية الصحيحة، مما يسمح لمستخدم موثّق واحد بقراءة ونسخ وحذف والكتابة فوق الأصول في مساحات عمل أخرى.
النظام الفرعي للأصول V2 في Plane وثق في slugs مساحات العمل ومعرفات UUID للأصول دون فرض عمليات التحقق المناسبة من العضوية، مما سمح لمستخدم مصادق واحد بقراءة الأصول ونسخها وحذفها والكتابة فوقها في مساحات عمل أخرى.
اكتشفت هذه الثغرة أثناء مراجعة Plane، منصة إدارة المشاريع مفتوحة المصدر، مع سؤال محدد جدًا في ذهني:
هل تقوم نقاط نهاية الأصول V2 بفرض حدود مساحات العمل فعليًا، أم أنها تثق في slugs مساحات العمل ومعرفات UUID للأصول التي يقدمها المهاجم إلى حد كبير جدًا؟
في هذه الحالة، كانت الإجابة لا.
كشف النظام الفرعي للأصول V2 في Plane عن ثغرتين مرتبطتين في التفويض كسرتا عزل مساحة العمل لأي مستخدم مصادق:
مما جعل إساءة استخدام الأصول عبر مساحات العمل ممكنة.
في إثبات المفهوم (PoC) الذي قمت بالتحقق منه، تمكن مستخدم عادي واحد في مساحة عمل Bravo من:
تم تخصيص CVE-2026-46558 لهذه الثغرة لاحقًا.
Plane: Plane على GitHub
CVE: CVE-2026-46558
أثر هذا على Plane، الذي يُقدم على موقعه الرسمي على أنه يُستخدم من قبل أكثر من 50,000 فريق حول العالم. كما تسلط Plane الضوء على اعتماد مفتوح المصدر قوي، بما في ذلك أكثر من 46,000 نجمة على GitHub و أكثر من 1,000,000 عملية سحب لـ Docker، وتظهر مؤسسات مثل Tencent و Accenture و Microsoft و Amazon.
مهاجم مصادق في مساحة العمل B ← نقطة نهاية الأصول V2 على مستوى مساحة العمل تثق في slug مساحة العمل المستهدفة ومعرف UUID للأصل دون التحقق المناسب من العضوية ← قراءة / تصحيح / حذف موقّعة مسبقًا ضد أصول مساحة العمل A + تدفق تكرار الأصول يثق في معرف UUID للأصل المرفوع المصدر ← كشف ونسخ وحذف واستبدال العلامات التجارية عبر مساحات العمل
Plane هي منصة إدارة مشاريع مفتوحة المصدر تُستخدم لإدارة:
وهذا يعني أن نظام الأصول الفرعي الخاص بها يقع على حدود ثقة حقيقية.
السؤال المهم هنا لم يكن ما إذا كانت Plane تدعم الرفع.
السؤال الحقيقي كان:
هل يفرض Plane عزل مساحة العمل عندما يشير مستخدم مصادق واحد إلى أصول مملوكة لمساحة عمل أخرى؟
في هذه الحالة، لم يفعل.
الكثير من مراجعات التطبيقات متعددة المستأجرين تركز أولاً على نقاط نهاية المسؤول الواضحة أو تحديثات الإعدادات المباشرة.
هذا يغفل فئة أخطاء شائعة وحقيقية جدًا:
الوصول إلى الكائنات الثانوية من خلال أنظمة الملفات أو الأصول المشتركة
من السهل ارتكاب الأخطاء في أنظمة الأصول لأنها غالبًا ما تجمع بين:
هذا هو بالضبط النوع من الأماكن الذي تضعف فيه حدود المستأجرين بصمت.
هذه المشكلة لم تكن حول تلف التخزين. لم تكن حول S3 نفسه. لم تكن حول معالجة MIME للرفع.
كانت فشلًا في حدود التفويض:
هذا كافٍ لإنشاء ثغرة حقيقية.
لم أتعامل مع Plane بالتجربة العمياء لنقاط نهاية عشوائية أو تخمين معرفات UUID بدون نموذج.
النهج الأقوى هو تحديد الحدود الواعدة للعزل أولاً.
بالنسبة لـ Plane، كان ذلك النظام الفرعي للأصول V2.
لماذا؟
لأن نظام الأصول المشترك يصبح خطيرًا عندما:
كان هذا هو الحدود الصحيحة للفحص.
وكان بالضبط المكان الذي عاشت فيه الثغرة.
كان هذا في الحقيقة فشلين في التفويض مرتبطين في نفس النظام الفرعي.
تم عرض مسارات الأصول على مستوى مساحة العمل من خلال:
apps/api/plane/app/urls/asset.py:50-56كانت المعالجات الضعيفة في:
apps/api/plane/app/views/asset/v2.py:314apps/api/plane/app/views/asset/v2.py:379apps/api/plane/app/views/asset/v2.py:400apps/api/plane/app/views/asset/v2.py:409المشكلة كانت بسيطة.
WorkspaceFileAssetEndpoint قبلت slug مساحة العمل ومعرف UUID للأصل، ثم حلت الكائنات مباشرة مثل:
workspace = Workspace.objects.get(slug=slug)
و:
asset = FileAsset.objects.get(id=asset_id, workspace__slug=slug)
دون فرض أن المتصل هو بالفعل عضو مخول في مساحة العمل المستهدفة أولاً.
هذا يعني أن نقطة النهاية لا تزال قادرة على:
لكائنات مساحة عمل أخرى.
تم تعيين مسار تكرار الأصول من خلال:
apps/api/plane/app/urls/asset.py:100-101المنطق الضعيف كان في:
apps/api/plane/app/views/asset/v2.py:736-780كان لمساحة العمل الوجهة معدّل تفويض. لكن البحث عن الأصل المصدر لم يكن لديه.
تم تحميل الكائن المصدر باستخدام:
original_asset = FileAsset.objects.filter(id=asset_id, is_uploaded=True).first()
هذا يعني أن المتصل يحتاج فقط:
لم يكن هناك تحقق من أن المتصل ينتمي إلى مساحة العمل المصدر التي تملك ذلك الأصل بالفعل.
هذا هو كل الثغرة الثانية.
الفرق المهم هو التأثير عبر مساحات العمل.
الكثير من أخطاء التفويض يتم التقليل من شأنها على النحو التالي:
"لا تزال تتطلب تسجيل الدخول"
هذا يغفل النقطة.
السؤال الحقيقي ليس:
"هل المتصل مصادق؟"
السؤال الحقيقي هو:
"هل المتصل مخول لمساحة العمل المحددة والأصل المحدد الذي يتم التعامل معه؟"
في Plane، كانت الإجابة لا.
هذا يحول ما قد يبدو كمعالجة عادية للكائنات إلى مشكلة أمنية حقيقية متعددة المستأجرين.
هناك فرق واضح بين:
كانت هذه المشكلة في الحالة الثانية بالتأكيد.
لقد قمت بالتحقق من المشكلة محليًا ضد Plane Community Edition 1.2.3 باستخدام مستخدمين عاديين في مساحتي عمل غير مرتبطتين:
alpha-20260323072017bravo-20260323072017استخدمت Alpha لإنشاء أصل خاص مرفوع مشروع في مشكلة.
معرف الأصل الخاص الذي تم التحقق منه في تشغيلتي كان:
6ed6ed62-d1b2-4399-8220-336c01b7d72c
كمستخدم Bravo، طلبت:
GET /api/assets/v2/workspaces/alpha-20260323072017/6ed6ed62-d1b2-4399-8220-336c01b7d72c/
أعادت Plane:
HTTP/1.1 302 Found
مع رابط URL تنزيل موقّع مسبقًا لأصل Alpha.
تطابق تجزئة الملف الذي تم تنزيله تمامًا مع أصل Alpha الخاص الأصلي:
original: 0d4d070f550ba59ba6b30bee62343cf68ea221af7034f131f72f6409cf5a598e
unauthorized read: 0d4d070f550ba59ba6b30bee62343cf68ea221af7034f131f72f6409cf5a598e
أثبت ذلك أن مسار القراءة عبر حدود مساحة العمل بنجاح.
كمستخدم Bravo، طلبت بعد ذلك:
POST /api/assets/v2/workspaces/bravo-20260323072017/duplicate-assets/6ed6ed62-d1b2-4399-8220-336c01b7d72c/
أعادت Plane:
HTTP/1.1 200 OK
وأنشأت أصلًا مكررًا في جانب المهاجم:
72d51497-ccc1-4546-ba14-28fae5d37dbb
تطابق تجزئة SHA-256 للملف المكرر تمامًا مع أصل Alpha الأصلي:
0d4d070f550ba59ba6b30bee62343cf68ea221af7034f131f72f6409cf5a598e
أثبت ذلك أن معرف UUID للأصل المصدر وحده كان كافيًا لنسخ محتوى عبر مساحة عمل إلى مساحة عمل يتحكم فيها المهاجم.
كمستخدم Bravo، أرسلت بعد ذلك: