
ميزة استيراد الصور عن بُعد في 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 لا يجلب فقط بايتات عشوائية ويتوقف. يقوم بالتحقق من الوسائط بعد الطلب.