FastJSON autoType is not support问题解决

news2024/11/17 19:32:08

概述

产品在使用内部的后台管理系统时反馈的问题。

于是登录平台,发现如下报错详情:
在这里插入图片描述

排查

经过分析,不难得知,请求是从gateway网关转发到对应的统计服务 statistics,此服务有个接口/api/statistics/data/overview。去ELK查找 statistics 服务的报错日志,没有发现ERROR日志。那就去 gateway 网关服务找,发现如下报错日志:

ERROR | o.s.b.a.web.reactive.error.AbstractErrorWebExceptionHandler | error | 122 | - [96c03c98] 500 Server Error for HTTP POST "/api/statistics/data/overview"
com.alibaba.fastjson.JSONException: autoType is not support. com.aba.rbac.modules.security.security.JwtUser
    at com.alibaba.fastjson.parser.ParserConfig.checkAutoType(ParserConfig.java:1542)
    at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:343)
    at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1430)
    at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1390)
    at com.alibaba.fastjson.JSON.parse(JSON.java:181)
    at com.alibaba.fastjson.JSON.parse(JSON.java:191)
    at com.alibaba.fastjson.JSON.parse(JSON.java:147)
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:252)
    at com.aba.gateway.filter.PermissionFilter.filter(PermissionFilter.java:123)

代码解析

报错的代码如下:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 后台操作权限过滤器
 **/
@Slf4j
@Component
public class PermissionFilter implements GlobalFilter, Ordered {

    @Autowired
    private RedisTemplate redisTemplate;
    @Resource
    private JwtTokenUtil jwtTokenUtil;
    @Value("${jwt.header}")
    private String tokenHeader;
    @Value("${gwb.referer}")
    private String imsHost;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String requestPath = request.getURI().getPath();
        HttpHeaders headers = request.getHeaders();
        // 初始值,默认为false,表示无权限
        AtomicBoolean isPermission = new AtomicBoolean(false);
        String username = headers.getFirst("username");
        String origin = headers.getFirst("origin");
        if (!StringUtils.isEmpty(origin)) {
            if (!origin.equals(imsHost) && !origin.contains("localhost")) {
                log.error("origin非法:{}", origin);
                throw new IllegalArgumentException("origin非法!");
            }
        }
		// 排除登录接口
        if (!requestPath.contains("/auth/login/ldap") && !requestPath.contains("/api/rbac")) {
            Assert.notNull(username, "header中的username不能为空");
            final String requestHeader = headers.getFirst(this.tokenHeader);
            Boolean invalid;
            if (StringUtils.isEmpty(requestHeader)) {
                log.error("token为空!");
                invalid = true;
            } else {
                try {
                    String authToken = requestHeader.substring(7);
                    invalid = jwtTokenUtil.isTokenExpired(authToken);
                    String tokenName = jwtTokenUtil.getUsernameFromToken(authToken);
                    if (!username.equals(tokenName)) {
                        Response<Void> response = Response.error(9642, "token非法!");
                        log.info("token中用户与username不一致!");
                        // 设置body
                        DataBuffer bodyDataBuffer = response.bufferFactory().wrap(JsonUtil.beanToJson(response).getBytes(StandardCharsets.UTF_8));
                        return response.writeWith(Mono.just(bodyDataBuffer));
                    }
                } catch (Exception e) {
                    log.error("jwt校验发生异常!", e);
                    invalid = true;
                }
            }
            if (invalid) {
                Response<Void> response = Response.error(9642, "token已失效!");
                log.info("token失效!");
                //设置body
                DataBuffer bodyDataBuffer = response.bufferFactory().wrap(JsonUtil.beanToJson(response).getBytes(StandardCharsets.UTF_8));
                return response.writeWith(Mono.just(bodyDataBuffer));
            }
            ValueOperations<String, Object> operations = redisTemplate.opsForValue();
            String postData = (String) operations.get(username);
            HashSet<String> roles;
            if (StringUtils.isBlank(postData)) {
                roles = Sets.newHashSet();
            } else {
            	// 报错行
                JSONObject jsonObject = JSON.parseObject(postData);
                roles = (HashSet<String>) jsonObject.get("roles");
            }
            if (roles.contains(requestPath)) {
                isPermission.set(true);
            } else {
                roles.forEach(role -> {
                    if (requestPath.contains(role)) {
                        isPermission.set(true);
                    }
                });
            }
            // 停止转发没有用户登录的请求
            if (!isPermission.get()) {
                Response<Void> response = Response.error(9641, "权限不足,请检查配置!");
                DataBuffer bodyDataBuffer = response.bufferFactory().wrap(JsonUtil.beanToJson(response).getBytes(StandardCharsets.UTF_8));
                return response.writeWith(Mono.just(bodyDataBuffer));
            }
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE;
    }
}

代码解析:
检查前端请求的header=origin是否合法,检查header=username是否为空,解析header里的token是否为空,是否过期,从token里解析得到的username与header=username是否一致。全部检查通过后,从Redis里获取该用户username所具备的权限,权限是一个set集合,表示该用户具备的全部请求URL路径。对比权限set<url>与用户请求的URL,是set集合里的元素,则放行,否则报错权限不足。

另外缓存数据是在登录时放在Redis里,也就是上面的/auth/login/ldap接口里,各种检查通过后,最后做的事情。代码略。

johnny.wong用户权限信息存入缓存中:com.aba.rbac.modules.security.security.JwtUser@32525c81

Google

与此同时,Google搜索得知。参考enable_autotype,FastJSON在1.2.24及之前的版本存在代码执行漏洞,当恶意攻击者提交一个精心构造的序列化数据到服务端时,由于FastJSON在反序列化时存在漏洞,可导致远程任意代码执行。

自1.2.25+版本,禁用部分autoType的功能,也就是@type这种指定类型的功能会被限制在一定范围内使用。反序列化对象时,需要检查是否开启autoType。没有开启就会报错。

复现

生产环境发现问题,单纯靠分析日志定位到问题,并能立马解决问题,那就不叫什么问题。

如果可以在本地开发环境复现问题,那就不是什么大问题。

在上面提到的报错行打个调试断点,如期出现报错,那就好办:
在这里插入图片描述
报错出现在JSON反序列化那一行:JSONObject jsonObject = JSON.parseObject(postData);。postData是Redis里面的缓存数据。来看一下Redis里的数据长什么样:
在这里插入图片描述
发现这里面的@type和报错提到的全路径名一模一样。也就是说,我现在遇到的问题,95%概率就是上面Google搜索到的反序列化漏洞场景问题。

此时再来看看 gateway 网关服务最近的更改:
在这里插入图片描述
移除gateway网关服务里pom文件指定的版本号,直接使用parentpom文件里指定的版本号:
在这里插入图片描述
即,从1.2.20升级到1.2.83。

之前gateway网关服务使用FastJSON 1.2.20版本时,实际上就存在反序列化漏洞。网关服务存在漏洞啊!!升级到1.2.83版本后,解决反序列化漏洞,但是需要手动开启autoType,autoType默认是关闭的,否则反序列化失败。

解决过程

基于上面的结论,加上本地可以复现问题,解决问题的思路当然简单。

全局

在肯定会执行的Spring Bean类里,增加如下static代码库,开启全局autoType:

@Configuration
public class RedisConfig {
    static {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    }
}

但是还是有报错:
在这里插入图片描述
从上面截图可知,此时的报错@type是另一个全路径包名。

稳住,不慌。

继续看官方文档,Google搜索官方GitHub issue。

附:报错的源码代码片段:

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);
        }
    }
}

全路径

全局开启有问题,那就开启白名单,指定一个个包名全路径:

static {
//        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    ParserConfig.getGlobalInstance().addAccept("com.aba.rbac.modules.security.security.JwtUser");
    ParserConfig.getGlobalInstance().addAccept("org.springframework.security.core.authority.SimpleGrantedAuthority");
}

无奈还是有报错:
在这里插入图片描述
报错源码如下:

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) {
            if (typeName.endsWith("Exception") || typeName.endsWith("Error")) {
                return null;
            }
            throw new JSONException("autoType is not support. " + typeName);
        }
    }
}

注:上面两次throw new JSONException("autoType is not support. " + typeName),都是在源码的方法

public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
}

里面。

根据上面的代码,不难得知修复方法如下,既要开启全局autoType,同时还需要手动指定下面这个全路径包名:

static {
    // https://github.com/alibaba/fastjson/wiki/enable_autotype
    ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    ParserConfig.getGlobalInstance().addAccept("com.aba.rbac.modules.security.security.JwtUser");
    ParserConfig.getGlobalInstance().addAccept("org.springframework.security.core.authority.SimpleGrantedAuthority");
}

注:为了确保这个static代码块肯定被执行,一定要放在Spring Bean扫描到的类里面,或者直接放在Spring Boot启动类里。

参考

  • FastJSON-autoType
  • FastJSON autoType漏洞
  • enable_autotype

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

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

相关文章

华为OD机试真题 Java 实现【支持优先级的队列】【2023 B卷 100分】

一、题目描述 实现一个支持优先级的队列&#xff0c;高优先级先出队列&#xff0c;同优先级时先进先出。 如果两个输入数据和优先级都相同&#xff0c;则后一个数据不入队列被丢弃。 队列存储的数据内容是一个整数。 二、输入描述 一组待存入队列的数据&#xff08;包含内…

Java官方笔记4类和对象

创建类 定义类Bicycle&#xff1a; public class Bicycle {// the Bicycle class has// three fieldspublic int cadence;public int gear;public int speed;// the Bicycle class has// one constructorpublic Bicycle(int startCadence, int startSpeed, int startGear) {gea…

李沐动手学习深度学习 2023年Win10 下安装 CUDA 和 Pytorch 跑深度学习(最新)

目录 一、安装Anaconda 1.下载Anaconda 测试是否安装成功 二、安装pytorch 验证pytorch是否安装成功 4.测试 3.配置pycharm 一、安装Anaconda 1.下载Anaconda 可以在官网下载&#xff0c;但是速度较慢&#xff0c;这里我选择了清华镜像源的下载 https://mirrors.tuna.t…

Gradio的web界面演示与交互机器学习模型,Blocks的事件侦听《7》

在第一篇文章我们就熟悉了Blocks的用法&#xff0c;使用Blocks比Interface更加灵活&#xff0c;这节重点关注Blocks里面的相关操作。 1、Blocks标准例子 import gradio as grdef greet(name):return "你好 " name "!"with gr.Blocks() as demo:name g…

简单的汉诺塔,神奇的预言,竟然需要5849亿年???(52)

小朋友们好&#xff0c;大朋友们好&#xff01; 我是猫妹&#xff0c;一名爱上Python编程的小学生。 和猫妹学Python&#xff0c;一起趣味学编程。 今日主题 汉诺塔 古印度大梵天传说 Python玩转汉诺塔 递归 汉诺塔 汉诺塔(Hanoi)是一个著名的益智游戏&#xff0c;也称…

规则引擎架构-基于aviator

目录 aviator使用场景ASM 字节码操控框架aviator 表达式例子debug表达式类生成过程b-ca生成的class文件 aviator使用场景 github地址&#xff1a;aviator 使用场景&#xff1a; 规则判断及规则引擎公式计算动态脚本控制集合数据 ELT 等 …… ASM 字节码操控框架 asm实现&a…

【Dubbo】Dubbo架构的演进过程分析

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化&#xff0c;文章内容兼具广度、深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于…

Go并发编程 Goroutine、Channel、Select、Mutex锁、sync、Atomic等

本文所有实例代码运行go版本&#xff1a;go version go1.18.10 windows/amd64 1 并发编程介绍 1.1 串行、并发、并行 串行&#xff1a;所有任务一件一件做&#xff0c;按照事先的顺序依次执行&#xff0c;没有被执行到的任务只能等待。最终执行完的时间等于各个子任务之和。…

效率神器!神级ChatGPT浏览器插件分享

大家好&#xff0c;我是卷了又没卷&#xff0c;薛定谔的卷的AI算法工程师「陈城南」~ 担任某大厂的算法工程师&#xff0c;带来最新的前沿AI知识和工具&#xff0c;欢迎大家交流~&#xff0c;后续我还会分享更多 AI 有趣工具和实用玩法&#xff0c;包括AI相关技术、ChatGPT、AI…

初识SpringBoot -- SpringBoot入门保姆级教程(一)

文章目录 前言一、初识SpringBoot1.SpringBoot简介2.用编译器IDEA创建SpringBoot项目3.在官网创建SpringBoot项目4.SpringBoot项目快速启动&#xff08;前后端分离基本能力&#xff09;5.了解SpringBoot起步依赖和启动类 总结 前言 为了巩固所学的知识&#xff0c;作者尝试着开…

RocketMQ消息消费

RocketMQ消息消费示例代码&#xff1a; public static void main(String[] args) throws InterruptedException, MQClientException {DefaultMQPushConsumer consumer new DefaultMQPushConsumer("please_rename_unique_group_name_4");consumer.setNamesrvAddr(&qu…

C++ 学习 ::【基础篇:15】:C++ 类的基本成员函数:析构顺序问题(全局/静态/局部量) 及 类类型(自定义类型)与析构函数

本系列 C 相关文章 仅为笔者学习笔记记录&#xff0c;用自己的理解记录学习&#xff01;C 学习系列将分为三个阶段&#xff1a;基础篇、STL 篇、高阶数据结构与算法篇&#xff0c;相关重点内容如下&#xff1a; 基础篇&#xff1a;类与对象&#xff08;涉及C的三大特性等&#…

Nginx配置块location及rewrite详解(遗憾)

文章目录 一、location配置块详解1.location 大致分为三类2.location 常用的匹配规则3.location 匹配的优先级4.location 匹配流程5.location 的实际使用&#xff08;1&#xff09;直接匹配网站根目录首页&#xff08;2&#xff09;处理静态文件请求&#xff08;3&#xff09;通…

函数式接口相关知识点

这里写目录标题 函数式接口简介以及注意点函数式接口作为方法的参数函数式接口作为方法参数常用的函数式接口Supplier简介具体代码操作 Consumer简介具体代码演示演示1演示2 Predicate接口简介以及接口中的方法text和negate方法and和or方法Function方法简介具体操作1具体操作2 …

SQL语句之DDL语言

说明&#xff1a;DDL&#xff08;Data Definition Language&#xff0c;数据定义语言&#xff09;&#xff0c;用来定义数据库对象(数据库、表)&#xff0c;包括了数据库和表的创建、查询、使用和删除操作。 一、数据库操作 新安装的数据库&#xff0c;默认有以下四个数据库&…

计算机网络-数据链路层

概念 结点&#xff1a;主机、路由器 链路&#xff1a;结点间物理通道 数据链路&#xff1a;结点间逻辑通道&#xff0c;控制数据传输协议的硬件和软件加到链路上构成数据链路 帧&#xff1a;链路层的协议数据单元&#xff0c;封装网络层数据报 数据链路层负责通过一条链路从一…

银行转账问题(死锁)

本文主要讲述死锁的一个经典案例—银行转账问题&#xff0c;并对该问题进行定位、修复。 1. 问题说明 当账户A对账户B进行转账时&#xff0c; 首先需要获取到两把锁&#xff1a;账户A和账户B的锁。获取两把锁成功&#xff0c;且余额大于0&#xff0c;则扣除转出人的余额&…

我记不住的那些C语言的struct知识

背景&#xff1a; 最近在重学C语言&#xff0c;目的是为了能看懂操作系统的底层代码&#xff0c;也为后续使用C语言开发一个类似redis数据库的中间件做准备&#xff0c;于是又重新踏上了学习C语言的道路&#xff0c;早在上学期间就学习过C语言&#xff0c;但是很久都不用了&…

ssm学习-spring01

Spring_day01 今日目标 掌握Spring相关概念完成IOC/DI的入门案例编写掌握IOC的相关配置与使用掌握DI的相关配置与使用1,课程介绍 对于一门新技术,我们需要从为什么要学、学什么以及怎么学这三个方向入手来学习。那对于Spring来说: 1.1 为什么要学? 从使用和占有率看 Spri…

使用 ChatGPT API 构建系统(一):分类

今天我学习了DeepLearning.AI的 Building Systems with the ChatGPT API 的在线课程&#xff0c;我想和大家一起分享一下该门课程的一些主要内容。 下面是我们通过Openai API来访问ChatGPT模型的主要代码&#xff1a; import openai#您的openai的api key openai.api_key YOUR…