spring-security和ObjectMapper(json反序列化)的白名单问题记录

我不是码农

这几天在弄一个统一的支持多租户的认证鉴权平台,就想到了使用oauth这种授权协议来进行,通过包含open-user信息的jwt来进行分发。使用这个遇到了一些坑,记录如下:

先说明一下情况,遇到的是fasterxml的json反序列化的白名单问题,由于处理json的框架容易受到攻击,经常出现一些反序列化漏洞,新出一个如果采用黑名单,则这个名单会不断庞大,需要不断更新,故此fasterxml采用了白名单的机制来实现json反序列化。

这个问题爆发在于使用了OAuth2AuthorizationService这个实现类,里面有new ObjectMapper();咱们不能控制它里面自己new的ObjectMapper,所以咱们必须重新定义一个OAuth2AuthorizationService,

@Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
                                                           RegisteredClientRepository registeredClientRepository,
                                                           ObjectMapper objectMapper) {
        JdbcOAuth2AuthorizationService service = new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
        JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper authorizationRowMapper = new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);
        authorizationRowMapper.setLobHandler(new DefaultLobHandler());
        authorizationRowMapper.setObjectMapper(objectMapper);
        service.setAuthorizationRowMapper(authorizationRowMapper);
        return service;
    }

这里注入ObjectMapper,我们就能对其进行定制化了。

//@Bean 这里可以看情况全局配置objectMapper,如果用到了springmvc,会干扰到springmvc的json解析
//可以把这段写到OAuth2AuthorizationService的Bean定义里面去
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return builder -> {
            //jackson反序列化 安全白名单
            builder.mixIn(OpenAppUser.class, OpenAppUserMixin.class);
            List<Module> securityModules = SecurityJackson2Modules.getModules(LocalDateTimeSerializerConfig.class.getClassLoader());
            securityModules.add(new OAuth2AuthorizationServerJackson2Module());
            builder.modulesToInstall(securityModules.toArray(new Module[securityModules.size()]));
        };
    }

可以去看看这几个Module,里面都是security定义的一些加入安全白名单的类。这里我自己定义了一个用户类,因为登录查询的用户就是使用的这个
这样就可以进行自定义用户对象的json安全反序列化了。

//实际上使用security和objectmapper的时候提供了三种方式来实现加入反序列化的白名单,在上面的那个加载Module代码里,首先都会调用
SecurityJackson2Modules.enableDefaultTyping(mapper);
//方法,然后里面调用
mapper.setDefaultTyping(createAllowlistedDefaultTyping());
//返回的是一个TypeResolverBuilder,这里的实现类是AllowlistTypeResolverBuilder
private static TypeResolverBuilder<? extends TypeResolverBuilder> createAllowlistedDefaultTyping() {
        TypeResolverBuilder<? extends TypeResolverBuilder> result = new AllowlistTypeResolverBuilder(
                ObjectMapper.DefaultTyping.NON_FINAL);
        result = result.init(JsonTypeInfo.Id.CLASS, null);
        result = result.inclusion(JsonTypeInfo.As.PROPERTY);
        return result;
    }
//AllowlistTypeResolverBuilder类里面的idResolver()方法返回的是TypeIdResolver,实现是一个AllowlistTypeIdResolver()
@Override
protected TypeIdResolver idResolver(MapperConfig<?> config, JavaType baseType, PolymorphicTypeValidator subtypeValidator, Collection<NamedType> subtypes, boolean forSer, boolean forDeser) {
    TypeIdResolver result = super.idResolver(config, baseType,subtypeValidator, subtypes, forSer, forDeser);
    return new AllowlistTypeIdResolver(result);
}
//重点就是这个类了,它里面的typeFromId方法
@Override
public JavaType typeFromId(DatabindContext context, String id) throws IOException {
    DeserializationConfig config = (DeserializationConfig) context.getConfig();
    JavaType result = this.delegate.typeFromId(context, id);
    String className = result.getRawClass().getName();
    if (isInAllowlist(className)) {
        return result;
    }
    boolean isExplicitMixin = config.findMixInClassFor(result.getRawClass()) != null;
    if (isExplicitMixin) {
        return result;
    }
    JacksonAnnotation jacksonAnnotation = AnnotationUtils.findAnnotation(result.getRawClass(),
            JacksonAnnotation.class);
    if (jacksonAnnotation != null) {
        eturn result;
    }
    throw new IllegalArgumentException("The class with " + id + " and name of " + className
                    + " is not in the allowlist. "
                    + "If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. "
                    + "If the serialization is only done by a trusted source, you can also enable default typing. "
                    + "See https://github.com/spring-projects/spring-security/issues/4370 for details");
}

可以看到security提供的这个方法里面的实现,三种方式分别是
1.isInAllowlist()是否在这个白名单集合里
2.是否有加入到MixIn,这个就是我们前面使用的方式
3.反序列化类上是否有@JacksonAnnotation注解
对于方式1,是个static集合不能修改;其他两种都可以

其实objectMapper提供是否安全是通过PolymorphicTypeValidator类来实现的,security上面默认使用的是

AllowlistTypeResolverBuilder(ObjectMapper.DefaultTyping defaultTyping) {
    super(defaultTyping,
        // we do explicit validation in the TypeIdResolver
        BasicPolymorphicTypeValidator.builder().allowIfSubType(Object.class).build());
}

这个意思是只要是object的子类都通过,所以security是在获取JavaType的时候自定义了一下白名单而没有采用objectMapper的方式。

阅读 400

java开发码农

0 声望
0 粉丝
0 条评论

java开发码农

0 声望
0 粉丝
文章目录
宣传栏