fix(video): 收紧 Redis Jackson default typing 白名单,修补反序列化攻击面

- 本模块通过 ZLM hook / GB28181 信令把大量外部输入写入 Redis,
  必须保证 default typing 的白名单足够紧。
- 新增 buildRedisObjectMapper:
    · 注册 JavaTimeModule 支持 LocalDateTime / LocalDate 等 Java 8
      时间类型,避免含 createTime 的对象序列化报错;
    · PropertyAccessor 从 ALL/ANY 收窄到 FIELD/ANY,配合 Lombok getter
      够用,同时降低经 setter 触发 gadget chain 的面;
    · BasicPolymorphicTypeValidator 的 allowIfSubType 原先是 Object.class
      (等价 LaissezFaireSubTypeValidator,Jackson 官方警告有 CVE 级
      反序列化风险,如 jackson-databind #2367 / #2996),改成按包名
      白名单:com.viewsh.* / com.alibaba.fastjson2.JSONObject|JSONArray
      / java.util|time|lang|math / 数组,严格收敛到业务真实存取范围。
- 两个 RedisTemplate Bean 都改用自构建的 ObjectMapper。
- 后续若遇到"新 DTO 反序列化不了",优先放进 com.viewsh 包下,
  不要回退到 allowIfSubType(Object.class)。
This commit is contained in:
lzh
2026-04-23 15:09:04 +08:00
parent a58ab1928e
commit 42d53bb02d

View File

@@ -1,5 +1,11 @@
package com.viewsh.module.video.framework.redis;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.viewsh.module.video.gb28181.bean.MobilePosition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -12,22 +18,65 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
* Video 模块专用 RedisTemplate 配置
* 独立 Bean 名称,避免覆盖框架的 Jackson 版本 RedisTemplate
*
* NOTE: Original WVP used FastJSON2 (GenericFastJsonRedisSerializer from fastjson2-extension-spring6).
* Temporarily using GenericJackson2JsonRedisSerializer as fallback until
* fastjson2-extension-spring6 is available in the local Maven repository.
* TODO: Switch back to GenericFastJsonRedisSerializer once the dependency is resolved.
* <p>ObjectMapper 注意点:
* 1. 注册 {@link JavaTimeModule}:支持 LocalDateTime / LocalDate 等 Java 8 时间类型
* (否则写入含 createTime 的对象会报 "not supported by default"
* 2. 启用 default typing存入时写入 @class 元信息,读出时才能反序列化成原类
* WVP 大量 Redis 用法依赖"存任意对象、读回来能强转"的能力)
* 3. typing 使用 BasicPolymorphicTypeValidator 做白名单校验,避免 gadget chain 攻击
*
* <p>NOTE: Original WVP used FastJSON2. 迁移到 Jackson 后需要手动对齐 JSR310 + default typing。
*/
@Configuration
public class RedisTemplateConfig {
/**
* 构建一份专用于 Redis 值序列化的 ObjectMapper
* 不复用全局 Spring Boot ObjectMapper因为 Redis 的 default typing 会改变 JSON 输出,
* 不想污染到业务接口的 JSON 响应
*/
private static ObjectMapper buildRedisObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
// 字段可见性只放开到 field 级(配合 Lombok getter 够用),不放开 setter/creator/is-getter
// 降低反序列化时被"非预期 setter"串通 gadget chain 的面
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
// default typing写入 @class 元信息以支持多态读回。
//
// 白名单要"够用 + 够紧"
// - 原先的 allowIfSubType(Object.class) 等价于 LaissezFaireSubTypeValidator
// Jackson 官方明确警告会触发 CVE 级 gadget chainjackson-databind #2367/#2996 等),
// 本模块通过 hook/SIP 信令把外部输入写进 Redis必须收紧
// - 按业务实际需要列举允许的 package
// 1) com.viewsh.* —— 本模块 + 框架公共结构CommonResult / PageResult 等)
// 2) com.alibaba.fastjson2.JSONObject/JSONArray —— WVP 历史代码存这两个类到 Redis
// 3) java.util / java.time / java.lang / java.math —— 集合/时间/数字基础类型
// - 后续如果遇到"新 DTO 反序列化不了",优先把类放进 com.viewsh 包名下,
// 不要再退回 allowIfSubType(Object.class)
BasicPolymorphicTypeValidator validator = BasicPolymorphicTypeValidator.builder()
.allowIfSubType("com.viewsh.")
.allowIfSubType("com.alibaba.fastjson2.JSONObject")
.allowIfSubType("com.alibaba.fastjson2.JSONArray")
.allowIfSubType("java.util.")
.allowIfSubType("java.time.")
.allowIfSubType("java.lang.")
.allowIfSubType("java.math.")
.allowIfSubTypeIsArray()
.build();
mapper.activateDefaultTyping(
validator,
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
return mapper;
}
@Bean("videoRedisTemplate")
public RedisTemplate<Object, Object> videoRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
// Using Jackson serializer as fallback (WVP originally used FastJSON2)
GenericJackson2JsonRedisSerializer jacksonSerializer = new GenericJackson2JsonRedisSerializer();
GenericJackson2JsonRedisSerializer jacksonSerializer =
new GenericJackson2JsonRedisSerializer(buildRedisObjectMapper());
redisTemplate.setValueSerializer(jacksonSerializer);
redisTemplate.setHashValueSerializer(jacksonSerializer);
// key serializer
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory);
@@ -38,7 +87,8 @@ public class RedisTemplateConfig {
public RedisTemplate<String, MobilePosition> videoRedisTemplateForMobilePosition(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, MobilePosition> redisTemplate = new RedisTemplate<>();
GenericJackson2JsonRedisSerializer jacksonSerializer = new GenericJackson2JsonRedisSerializer();
GenericJackson2JsonRedisSerializer jacksonSerializer =
new GenericJackson2JsonRedisSerializer(buildRedisObjectMapper());
redisTemplate.setValueSerializer(jacksonSerializer);
redisTemplate.setHashValueSerializer(jacksonSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer());