
ميزة استيراد الصور عن بُعد في Penpot تسمح لمحرر ملفات موثوق بتحويل ميزة وسائط عادية إلى ثغرة SSRF منشؤها الخادم الخلفي (backend-origin SSRF)، وذلك لأن عناوين URL التي يتحكم فيها المهاجم عبرت إلى مسار جلب خادم يتبع إعادة التوجيه (redirect-following) بدون فلترة للوجهة.
استيراد الصور عن بُعد في Penpot سمح لمحرر ملفات مصادق بتحويل ميزة وسائط عادية إلى SSRF من أصل الخادم الخلفي لأن عناوين URL التي يتحكم بها المهاجم عبرت إلى مسار جلب خادم يتبع إعادة التوجيه دون تصفية الوجهة.
اكتشفت هذه المشكلة أثناء مراجعة Penpot، منصة التعاون في التصميم والكود مفتوحة المصدر، مع سؤال محدد للغاية في ذهني:
ماذا يحدث عندما تسمح أداة تعاون في التصميم لأحد المستخدمين بتمرير عنوان URL لصورة عن بُعد إلى الخادم الخلفي لجلبها؟
في هذه الحالة، أدى ذلك السؤال إلى خطأ حقيقي.
قبل تدفق استيراد الصور عن بُعد في Penpot عنوان URL يتحكم به المستخدم وتسبب في جلب الخادم الخلفي له من سياق شبكة الخادم دون فرض قيود على الوجهات المستهدفة للحلقة المحلية أو الشبكة الخاصة. كما اتبع عميل HTTP المشترك إعادة التوجيه تلقائيًا.
حول ذلك ميزة وسائط عادية إلى بدائية SSRF مصادق من أصل الخادم الخلفي، وأصبحت في النهاية CVE-2026-45806.
Penpot: Penpot على GitHub
CVE: CVE-2026-45806
CVSS: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
أثر هذا على Penpot. على موقعها الرسمي وحزمة الوسائط، تقدم Penpot نفسها على أنها ذات قاعدة مستخدمين متزايدة تزيد عن مليون مستخدم وتقول أن عشرات الآلاف من المؤسسات تستخدمها، بما في ذلك Blender و Mozilla و Fedora و NTT Data و MIT و Société Générale و Cisco و Fujitsu و Indra و ByteDance.
محرر ملفات مصادق -> عنوان URL لصورة عن بُعد يتحكم به المهاجم -> إنشاء كائن وسائط ملف من عنوان URL -> جلب صورة تنزيل من الخادم الخلفي مع تمكين إعادة التوجيه -> الطلب النهائي يصل إلى نقطة نهاية صورة داخلية فقط -> SSRF من أصل الخادم الخلفي / قابلية الوصول الداخلي
Penpot هي منصة تعاون في التصميم والكود مفتوحة المصدر.
تتعامل مع أشياء مثل:
هذا يعني أن مسار استيراد الوسائط الخاص بها يقع على حدود ثقة حقيقية.
السؤال المهم هنا لم يكن ما إذا كان Penpot يدعم استيراد الصور عن بُعد.
السؤال الحقيقي كان:
هل يقيد Penpot أين يُسمح للخادم الخلفي بالاتصال عندما يستورد المستخدم صورة عن بُعد؟
في هذه الحالة، لم يفعل.
كثير من الناس يقللون من شأن ميزات الاستيراد عن بُعد.
هذا خطأ.
في اللحظة التي يقبل فيها تطبيق:
فإنه يخلق حدود ثقة صادرة حقيقية.
هذا هو المشكلة هنا.
لم يكن هذا الخطأ في عرض الصور. لم يكن في تخزين الملفات. لم يكن في فحوصات الأذونات العادية لتحرير ملف.
كان فشلًا كلاسيكيًا في ثقة الخادم الخلفي:
هذا كافٍ لخلق ثغرة حقيقية.
لم أقترب من Penpot باختبار عشوائي لطرق RPC أو البحث عن الأعطال أولاً.
النهج الأقوى كان تحديد الحدود الأمنية الأكثر وعدًا.
بالنسبة لـ Penpot، كانت هذه استيراد الوسائط عن بُعد.
لماذا؟
لأن هذه الميزة تجمع بين:
تلك كانت الحدود الصحيحة للتفتيش.
وكانت بالضبط مكان وجود الخطأ.
يتلخص الخطأ في سلسلة ثقة صغيرة.
في الواجهة الأمامية:
(defn upload-media-url
[name file-id url]
(rp/cmd!
:create-file-media-object-from-url
{:name name
:file-id file-id
:url url
:is-local true}))
يتم إرسال url التي يتحكم بها المستخدم مباشرة في استدعاء RPC.
ثم في الخادم الخلفي:
(sv/defmethod ::create-file-media-object-from-url
...
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(files/check-edition-permissions! pool profile-id file-id)
...
(let [_ (files/get-minimal-file cfg file-id)
mobj (create-file-media-object-from-url cfg (assoc params :profile-id profile-id))])
و:
(defn- create-file-media-object-from-url
[cfg {:keys [url name] :as params}]
(let [content (media/download-image cfg url)
يتحقق الخادم الخلفي من أن المتصل يمكنه تحرير الملف الهدف، ثم يمرر عنوان URL الذي يتحكم به المهاجم إلى media/download-image.
تنفيذ الجلب موجود هنا:
(defn download-image
"قم بتنزيل صورة من URI المقدم وإرجاع كائن الإدخال الوسائطي"
[{:keys [::http/client]} uri]
...
(http/req! client
{:method :get :uri uri}
{:response-type :input-stream})
ويتم تكوين عميل HTTP المشترك كالتالي:
(http/build-client {:connect-timeout 30000
:follow-redirects :always}))
هذا هو الثغرة بأكملها:
لأن المهاجم يحتاج فقط إلى:
سلسلة الهجوم واضحة:
هذا هو الخطأ بأكمله.
الفرق المهم هو مكان حدوث الطلب.
السؤال ليس:
"هل يمكن لـ Penpot استيراد الصور من عناوين URL؟"
السؤال الحقيقي هو:
"هل يمكن لمستخدم مصادق أن يجعل خادم Penpot الخلفي يتصل بوجهات داخلية لا ينبغي للمستخدم أن يتمكن من الوصول إليها من خلال التطبيق؟"
في هذه الحالة، كانت الإجابة نعم.
هذا مهم لأنه يوجد فرق حقيقي بين:
التحقق من الصورة لا يزيل هذا الفرق.
إنه يضيق بعض حالات سرقة البيانات المباشرة، لكنه لا يزيل حالة SSRF أو كسر حدود الشبكة.
لقد قمت بالتحقق من هذه المشكلة باستخدام إثبات محلي خاضع للرقابة مرتبط مباشرة بمسار كود Penpot الذي تمت مراجعته.
الهدف لم يكن ضرب بنى تحتية لجهات خارجية. الهدف كان إثبات الخاصية الأمنية بالضبط:
قمت ببناء مدقق Java مستقل يعكس السلوك ذي الصلة:
content-type و content-lengthقمت بالتحقق من حالتين.
طلب المدقق:
http://127.0.0.1:7790/internal.png
النتيجة الملاحظة:
http://127.0.0.1:7790/internal.pnghttp://127.0.0.1:7790/internal.png200image/pngأثبت ذلك أن منطق الجلب بنمط الاستيراد قبل نقطة نهاية صورة داخلية فقط مباشرة.
ثم طلب المدقق:
http://localhost:7791/redirect-to-internal
أعادت تلك النقطة إعادة توجيه HTTP إلى:
http://127.0.0.1:7790/internal.png
النتيجة الملاحظة:
http://localhost:7791/redirect-to-internalhttp://127.0.0.1:7790/internal.png200image/pngسجل المستمع الداخلي فقط الطلب المعاد توجيهه.
أثبت ذلك الادعاء الأكثر أهمية:
كان الحمولة هنا بسيطة عمدًا:
هذا مهم لأن Penpot لا يجلب فقط بايتات عشوائية ويتوقف. يقوم بالتحقق من الوسائط بعد الطلب.
لذا فإن الإثبات الصحيح لم يكن:
"الخادم الخلفي يمكنه محاولة الاتصال بمكان ما"
الإثبات الأقوى كان:
"الخادم الخلفي يمكن جعله يتصل بمكان داخلي ويكمل الطلب بنجاح تحت نفس قيود الصور التي تتوقعها الميزة"
هذا بالضبط ما أظهره التحقق.
رد الفعل الشائع على ثغرات SSRF مثل هذه هو:
"الهدف لا يزال يجب أن يعيد صورة"
هذه الملاحظة صحيحة ولكنها غير مكتملة.
لا تزيل الثغرة.
إنها فقط تخبرك أي الأهداف الداخلية هي الأكثر فائدة مباشرة.
لا تزال هذه المشكلة تمكن من:
لا يزال هذا كسرًا حقيقيًا لحدود الأمان.
خاصة في البيئات المستضافة ذاتيًا، غالبًا ما توجد الخدمات الداخلية خلف تلك الحدود تحديدًا.
تم تعيين درجة عالية (High) للثغرة في النهاية على CVSS:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
هذا التصنيف منطقي.
الادعاء ليس أن مهاجمًا غير مصادق يمكنه اختراق كل نشر لـ Penpot فورًا من الصفر.
الادعاء هو أن أي محرر ملفات مصادق عادي يمكنه تحويل Penpot إلى بدائية طلب خادم خلفي ضد وجهات داخلية، بما في ذلك الوصول بمساعدة إعادة التوجيه إلى أهداف الحلقة المحلية والشبكة الخاصة.
كان هناك بعض النقاش حول الشدة أثناء الإفصاح، بشكل رئيسي حول:
تلك قيود عادلة للمناقشة.
لكنها لا تزيل المشكلة الأساسية:
هذه ثغرة SSRF حقيقية وقابلة للدفاع عنها.
الإصلاح المهم هنا ليس التعامل الأكثر صرامة مع MIME.
الإصلاح الحقيقي هو سياسة الوجهة الصادرة.
المعالجة الصحيحة لهذه الفئة من الثغرات تحتاج إلى:
http و httpslocalhostهذا هو اتجاه الإصلاح الصحيح لأن هذه لم تكن ثغرة في تحليل الصور. كانت ثغرة في حدود ثقة الشبكة.
تم الإبلاغ عن هذه المشكلة بشكل خاص من خلال تدفق الإبلاغ الأمني في GitHub.
تضمن التقرير:
أكد المشرفون على المشكلة وبدأوا العمل على حلها.
تم تعيين الثغرة لاحقًا:
CVE-2026-45806
الدرس الرئيسي بسيط:
استيراد الوسائط عن بُعد هو حد ثقة صادر، وليس مجرد ميزة راحة
العديد من المطورين يفكرون من حيث:
تلك تفاصيل تنفيذ.
السؤال الأمني الحقيقي هو:
أين يُسمح للخادم الخلفي بالاتصال نيابة عن المستخدم؟
إذا لم يتم الإجابة على هذا السؤال بشكل صريح، تصبح ميزات مثل الاستيراد عن بُعد أسطح SSRF افتراضيًا.
تعزز هذه الثغرة أيضًا شيئًا مهمًا حول مراجعة SSRF:
هذا هو الاستنتاج الحقيقي.
لم تكن هذه الثغرة حول حمولة مبهرجة.
كانت حول طرح سؤال حدود الثقة الصحيح.
Penpot سمح لمحرر ملفات مصادق بتوفير عنوان URL لصورة عن بُعد، ووثق الخادم الخلفي في ذلك العنوان URL أكثر مما ينبغي. معالجة إعادة التوجيه فعلت الباقي.
لهذا أصبحت CVE-2026-45806.