fastjson 远程代码执行漏洞(HTTP_Java静态调用_java.lang.Runtime_远程代码执行)

本文探讨了FastJSON 1.1.42版本中存在的远程代码执行安全漏洞,通过升级至1.2.30版本成功解决。文章详细介绍了漏洞触发机制及修复方法,包括全局过滤@type关键字。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天公司代码安全测试提出来一个问题:HTTP_Java静态调用_java.lang.Runtime_远程代码执行!

百度查询资料,发现是fastjson架包的安全漏洞问题,在com.alibaba.fastjson原来使用的是1.1.42版本的!

我升级到了1.2.30,测试发现确实解决了该问题!

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.30</version>
        </dependency>

看某博客主介绍fastjson在1.2.24以及之前版本存在远程代码执行高危安全漏洞,为了保证系统安全,请升级到1.2.28/1.2.29或者更新版本。

官方公告:https://github.com/alibaba/fastjson/wiki/security_update_20170315

触发漏洞需要构造一个特殊的json对象,这种应用场景多出现在C/S接口交互。公告中说道:"安全升级包禁用了部分autotype的功能,也就是"@type"这种指定类型的功能会被限制在一定范围内使用。"

当反序列化一个特殊的json字符串为java对象时可以实例化”任意“(有几个类如Thread在黑名单除外)的java对象并调用部分的java方法。

比如序列号如下json字符串(键包含了"@type" JsonTypeInfo类中的注解)时,fastjson会创建一个java对象实!

String json = "{\"@type\":\"java.lang.Runtime\"}";
Object obj = JSON.parseObject(json, Object.class);
System.out.println(obj);

执行上面的代码会发现输出了:java.lang.Runtime@194e4c9a,不过并不是就可以执行任意的代码了,因为得找一个触发点给方法传参。查了下fastjson的代码,发现可以直接在后面@type后面写方法名称和参数!

String json = "{\"@type\":\"org.javaweb.test.controller.Test\",\"cmd\":{\"@type\":\"java.lang.Runtime\"}}";

 

比如Test类有个setCmd方法可以传入自定义参数。但是JavaBeanInfo在反序列化操作的时候限定了很多条件,比如不能是静态方法、而且必须是setXXX方法,不过static类的成员变量可以直接修改值。

修复方法:

1、升级fastjson版本(注意可能有兼容问题,参见:https://github.com/alibaba/fastjson/wiki/incompatible_change_list)

2、全局过滤"@type"关键字

原文地址:

<think>我们使用FastJson的SerializeConfig全局配置了将Integer转换为String的序列化器。但是,现在需要让某个特定的类不进行这种转换,即保持Integer类型不变。思路:我们可以通过自定义一个注解,然后在序列化时检查类是否有该注解,如果有则跳过全局的转换。具体步骤:1.定义一个注解(例如@DisableIntegerToString),用于标记不需要转换的类。2.自定义一个ObjectSerializer,在序列化时判断当前对象所属的类是否带有该注解。如果有,则使用默认的序列化方式;否则,使用我们定义的IntegerToStringSerializer。3.在全局配置中,我们仍然使用自定义的SerializeConfig,但是我们需要修改IntegerToStringSerializer,使其在序列化时先检查类上的注解。但是注意,我们原来配置的是针对Integer类型的全局序列化器,现在需要根据不同的类进行不同的处理。因此,我们可以创建一个代理序列化器,在内部根据目标对象的类进行判断。另一种思路:我们可以在全局配置中仍然使用我们自定义的序列化器,但是在这个序列化器内部,判断当前要序列化的对象是否属于特定的类,如果是则跳过转换。然而,更合理的方式是使用FastJson的注解机制,但是FastJson没有提供直接跳过全局序列化器的注解。因此,我们采用代理序列化器的方式。具体实现:步骤1:定义注解```javaimportjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public@interfaceDisableIntegerToString{}```步骤2:修改全局序列化器,改为使用一个代理序列化器,这个代理序列化器会根据目标对象的类是否有注解来决定是否进行转换。我们创建一个代理的序列化器(ProxyIntegerSerializer):```javaimportcom.alibaba.fastjson.serializer.JSONSerializer;importcom.alibaba.fastjson.serializer.ObjectSerializer;importjava.io.IOException;importjava.lang.reflect.Type;publicclassProxyIntegerSerializerimplementsObjectSerializer{//默认的Integer序列化器(即FastJson原有的)privatefinalObjectSerializerdefaultSerializer;//自定义的转换为字符串的序列化器privatefinalObjectSerializerintegerToStringSerializer;publicProxyIntegerSerializer(ObjectSerializerdefaultSerializer,ObjectSerializerintegerToStringSerializer){this.defaultSerializer=defaultSerializer;this.integerToStringSerializer=integerToStringSerializer;}@Overridepublicvoidwrite(JSONSerializerserializer,Objectobject,ObjectfieldName,TypefieldType,intfeatures)throwsIOException{//如果当前正在序列化的对象是某个特定类的属性,我们需要检查这个类(即fieldName对应的类)是否有注解//但是注意:这里fieldType可能是Integer,而我们需要知道的是这个Integer属性所属的类(即父对象)是否有注解//然而,在序列化一个Integer属性时,我们无法直接获取到其所属的对象。所以我们需要改变思路:在序列化对象时,检查该对象的类是否有注解。//但是,这里我们序列化的是Integer类型的值,它没有类注解。因此,我们需要在序列化对象时,根据对象的类来决定其属性的序列化方式。//所以,我们需要修改方案:不在Integer序列化器里做判断,而是在对象的序列化过程中做判断。//由于这个思路比较复杂,我们换一个方案:不使用全局的Integer序列化器,而是使用一个自定义的JavaBeanSerializer,在序列化对象时,根据类注解来决定每个属性的序列化方式。//然而,FastJson并没有提供这么细粒度的控制。因此,我们回到最初的需求:我们想要某个类的所有Integer属性都不转换为字符串。//我们可以这样:在全局配置中,对于Integer类型仍然使用代理序列化器,然后在这个代理序列化器中,我们获取当前正在序列化的对象(即属性所属的对象)的类,判断这个类是否有注解。//在FastJson中,我们可以通过serializer.getContext()获取当前序列化的对象。但是,注意:序列化一个Integer属性时,context就是包含该属性的对象。Objectcontext=serializer.getContext().getObject();if(context!=null){Class<?>contextClass=context.getClass();if(contextClass.isAnnotationPresent(DisableIntegerToString.class)){//如果上下文对象(即包含当前属性的对象)的类有@DisableIntegerToString注解,则使用默认序列化defaultSerializer.write(serializer,object,fieldName,fieldType,features);return;}}//否则,使用转换为字符串的序列化器integerToStringSerializer.write(serializer,object,fieldName,fieldType,features);}}```步骤3:在配置中,将Integer的序列化器设置为我们的代理序列化器。但是,注意:我们原来的IntegerToStringSerializer需要保留,作为代理序列化器中的一个备选。步骤4:修改configureMessageConverters方法,将代理序列化器注册到Integer.class上。但是,这个方案有一个问题:序列化一个顶层的Integer(不是对象的属性)时,context为null,那么就会使用integerToStringSerializer,这符合预期吗?如果我们需要序列化的Integer是某个对象的属性,那么context就是那个对象。如果直接序列化一个Integer,则context为null,那么就会使用转换。根据需求,我们可能希望直接序列化一个Integer时也保持原样(不转换)?或者转换?根据原需求,我们只希望某个类不转换,其他情况都要转换。那么直接序列化的Integer(不属于任何对象)应该转换,因为没有任何注解可以标记它。所以这样处理是合理的。实现:修改configureMessageConverters方法:```javapublicvoidconfigureMessageConverters(List<HttpMessageConverter<?>>converters){SerializeConfigserializeConfig=SerializeConfig.getGlobalInstance();//获取FastJson默认的Integer序列化器ObjectSerializerdefaultIntegerSerializer=serializeConfig.get(Integer.class);if(defaultIntegerSerializer==null){defaultIntegerSerializer=com.alibaba.fastjson.serializer.IntegerCodec.instance;}//创建我们原来的IntegerToStringSerializer(将Integer转为String的序列化器)ObjectSerializerintegerToStringSerializer=newIntegerToStringSerializer();//创建代理序列化器ObjectSerializerproxySerializer=newProxyIntegerSerializer(defaultIntegerSerializer,integerToStringSerializer);//将Integer.class的序列化器设置为代理序列化器serializeConfig.put(Integer.class,proxySerializer);FastJsonConfigfastJsonConfig=newFastJsonConfig();fastJsonConfig.setCharset(StandardCharsets.UTF_8);fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue,SerializerFeature.WriteNullListAsEmpty,SerializerFeature.WriteDateUseDateFormat,SerializerFeature.DisableCircularReferenceDetect);fastJsonConfig.setSerializeConfig(serializeConfig);FastJsonHttpMessageConverterfastJsonConverter=newFastJsonHttpMessageConverter();fastJsonConverter.setFastJsonConfig(fastJsonConfig);fastJsonConverter.setSupportedMediaTypes(Lists.newArrayList(MediaType.APPLICATION_JSON));converters.add(fastJsonConverter);}```步骤5:在需要排除的类上加上@DisableIntegerToString注解。例如:```java@DisableIntegerToStringpublicclassMyClass{privateIntegermyInt;//这个字段将不会转换为字符串}```注意:这个方案依赖于序列化时获取上下文对象,而FastJson在序列化属性时确实会设置上下文。但是,如果序列化一个Integer数组,那么数组中的每个元素在序列化时,上下文是数组对象,而数组对象的类不会有注解(除非你给数组类加注解,这通常不可能)。所以,如果有一个类包含一个Integer数组,那么我们希望这个数组中的整数都不转换,那么需要在这个类上加上注解。另外,如果序列化一个Map,其中包含Integer值,那么上下文就是这个Map。而Map的类不会有注解,所以这些Integer值会被转换。如果希望Map中的某些Integer不转换,这个方案不支持。因此,这个方案适用于标注在JavaBean上,对于Bean中的Integer字段有效。如果需求更复杂,可能需要更高级的定制。最后,我们还需要原来的IntegerToStringSerializer的实现,假设它如下:```javaimportcom.alibaba.fastjson.serializer.ObjectSerializer;importcom.alibaba.fastjson.serializer.SerializeWriter;importcom.alibaba.fastjson.serializer.SerializerFeature;importjava.io.IOException;importjava.lang.reflect.Type;publicclassIntegerToStringSerializerimplementsObjectSerializer{@Overridepublicvoidwrite(JSONSerializerserializer,Objectobject,ObjectfieldName,TypefieldType,intfeatures)throwsIOException{SerializeWriterout=serializer.out;if(object==null){out.writeNull();return;}//将Integer转换为字符串输出out.writeString(object.toString());}}```这样,我们就实现了在特定类上禁用Integer到String的转换。但是,请注意:这个代理序列化器会检查包含该Integer字段的对象(即父对象)是否有注解,而不是检查Integer字段所在的类(因为Integer字段所在的类就是Integer,不可能有注解)。所以,我们在父类上标注注解。总结:通过代理序列化器,我们根据父对象的类是否有注解来决定是否进行转换,从而实现了对特定类的排除。注意:由于FastJson的序列化上下文机制,在序列化集合类型(如List<Integer>)时,上下文是集合对象,而集合对象没有我们的注解,所以集合中的整数会被转换为字符串。如果你希望某个类的某个List<Integer>字段不转换,那么需要在这个类上标注注解。这样,当序列化这个类的对象时,当序列化到该List字段时,上下文就是这个类的对象(有注解),所以List中的整数不会转换?但是,实际上,在序列化List字段时,我们会先序列化该字段(此时上下文是父对象,有注解),然后序列化List中的每个元素时,上下文是List对象(没有注解)。所以,这个方案对集合内部的整数无效。因此,如果需求包括集合中的整数,那么我们需要更复杂的处理。由于时间关系,这里先提供基础解决方案。如果确实需要支持集合,可以考虑在代理序列化器中向上查找多个上下文(但FastJson的上下文栈并不直接提供这样的支持)。所以,请根据实际需求选择方案。如果只需要处理普通JavaBean的Integer字段,这个方案足够。如果遇到集合类型,可以考虑在父对象中不使用List<Integer>,而是使用一个自定义的整数包装类,并在这个包装类上标注注解。但这可能不切实际。另一种思路:修改序列化器,当序列化一个Integer时,检查当前线程的调用栈,看是否在序列化某个特定类的过程中。这种方法性能较差,且不够可靠。因此,在需求明确的情况下,建议仅对普通JavaBean使用此方案。如果问题复杂,可能需要考虑其他序列化框架(如Jackson)的定制方式。以上方案供参考。如果遇到问题,请根据实际调试情况进行调整。</think>### 自定义序列化排除策略 在 FastJSON 全局配置中将所有 `Integer` 转换为 `String` 的情况下,要为特定类排除此转换,需要实现自定义序列化逻辑。以下是完整的解决方案: #### 1. 自定义序列化器(带类过滤功能) ```java import com.alibaba.fastjson.serializer.JSONSerializer; import com.alibaba.fastjson.serializer.ObjectSerializer; import com.alibaba.fastjson.serializer.SerializeWriter; import java.io.IOException; import java.lang.reflect.Type; import java.util.Set; import java.util.HashSet; public class FilterableIntegerSerializer implements ObjectSerializer { // 存储需要排除转换的类 private static final Set<Class<?>> excludedClasses = new HashSet<>(); private final ObjectSerializer defaultSerializer; public FilterableIntegerSerializer(ObjectSerializer defaultSerializer) { this.defaultSerializer = defaultSerializer; } // 添加要排除的类 public static void addExcludedClass(Class<?> clazz) { excludedClasses.add(clazz); } @Override public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException { // 获取当前序列化对象的上下文类 Class<?> contextClass = serializer.getContext().getClass(); // 检查是否在排除列表中 if (excludedClasses.contains(contextClass)) { // 使用默认序列化器(不转换) defaultSerializer.write(serializer, object, fieldName, fieldType, features); } else { // 自定义序列化逻辑(转换为字符串) SerializeWriter out = serializer.out; if (object == null) { out.writeNull(); } else { out.writeString(object.toString()); } } } } ``` #### 2. 配置类更新(使用自定义序列化器) ```java import com.alibaba.fastjson.serializer.SerializeConfig; import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.nio.charset.StandardCharsets; import java.util.List; @Configuration public class FastJsonConfigurer implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { // 获取全局序列化配置 SerializeConfig serializeConfig = SerializeConfig.getGlobalInstance(); // 获取默认的 Integer 序列化器 ObjectSerializer defaultIntegerSerializer = serializeConfig.get(Integer.class); if (defaultIntegerSerializer == null) { defaultIntegerSerializer = com.alibaba.fastjson.serializer.IntegerCodec.instance; } // 创建并配置自定义序列化器 FilterableIntegerSerializer customSerializer = new FilterableIntegerSerializer(defaultIntegerSerializer); // 添加要排除的类(例如:SpecialClass) customSerializer.addExcludedClass(SpecialClass.class); // 注册自定义序列化器 serializeConfig.put(Integer.class, customSerializer); // 配置 FastJSON FastJsonConfig fastJsonConfig = new FastJsonConfig(); fastJsonConfig.setCharset(StandardCharsets.UTF_8); fastJsonConfig.setSerializerFeatures( SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullListAsEmpty, SerializerFeature.WriteDateUseDateFormat, SerializerFeature.DisableCircularReferenceDetect ); fastJsonConfig.setSerializeConfig(serializeConfig); // 创建消息转换器 FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter(); fastJsonConverter.setFastJsonConfig(fastJsonConfig); fastJsonConverter.setSupportedMediaTypes(List.of(MediaType.APPLICATION_JSON)); // 添加转换器(优先使用 FastJSON) converters.add(0, fastJsonConverter); } } ``` #### 3. 使用注解标记排除类(备选方案) 如果需要更灵活的类排除方式,可以使用注解: ```java import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface SkipIntegerConversion { } ``` 然后在序列化器中检查注解: ```java // 在 FilterableIntegerSerializer 的 write 方法中添加: @Override public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException { Class<?> contextClass = serializer.getContext().getClass(); // 检查类是否有排除注解 boolean skipConversion = contextClass.isAnnotationPresent(SkipIntegerConversion.class); if (skipConversion) { defaultSerializer.write(serializer, object, fieldName, fieldType, features); } else { // 转换逻辑... } } ``` 在需要排除的类上添加注解: ```java @SkipIntegerConversion public class SpecialClass { private Integer id; private String name; // 其他字段... } ``` ### 方案解析 1. **核心机制**: - 使用自定义的 `FilterableIntegerSerializer` 包装默认序列化器 - 通过 `serializer.getContext()` 获取当前序列化的对象实例 - 根据对象的类决定是否应用转换 2. **类排除策略**: - **静态注册**:使用 `excludedClasses` 集合注册要排除的类 - **注解驱动**:使用自定义注解标记需要排除的类 - **动态判断**:在序列化时实时判断上下文对象类型 3. **执行流程**: ```mermaid sequenceDiagram participant Client as 客户端请求 participant Serializer as 自定义序列化器 participant FastJSON as FastJSON引擎 Client->>FastJSON: 序列化对象请求 FastJSON->>Serializer: 调用write方法 Serializer->>Serializer: 检查对象类是否在排除列表 alt 需要排除 Serializer->>FastJSON: 使用默认序列化 else 需要转换 Serializer->>FastJSON: 转换为字符串 end FastJSON->>Client: 返回JSON响应 ``` 4. **注意事项**: - 排除的是整个类的转换,不是单个字段 - 嵌套对象中的 Integer 字段也会被排除 - 确保在序列化前注册排除类(应用启动时) ### 性能优化建议 对于高性能场景: ```java // 使用 ClassValue 缓存判断结果 private static final ClassValue<Boolean> exclusionCache = new ClassValue<Boolean>() { @Override protected Boolean computeValue(Class<?> type) { return excludedClasses.contains(type) || type.isAnnotationPresent(SkipIntegerConversion.class); } }; // 在 write 方法中使用: boolean skipConversion = exclusionCache.get(contextClass); ``` ### 测试验证 ```java public class SerializationTest { @Test public void testIntegerConversion() { // 创建测试对象 NormalClass normal = new NormalClass(123); SpecialClass special = new SpecialClass(456); // 序列化 String normalJson = JSON.toJSONString(normal); String specialJson = JSON.toJSONString(special); // 验证结果 assertTrue(normalJson.contains("\"value\":\"123\"")); assertTrue(specialJson.contains("\"value\":456")); } static class NormalClass { private Integer value; // 构造器/getter/setter } @SkipIntegerConversion // 或通过 addExcludedClass 注册 static class SpecialClass { private Integer value; // 构造器/getter/setter } } ``` ### 备选方案:使用 PropertyPreFilter ```java // 创建属性过滤器 SerializeFilter filter = new PropertyPreFilter() { @Override public boolean apply(JSONSerializer serializer, Object source, String name) { // 特殊类不转换Integer字段 if (source instanceof SpecialClass && "integerField".equals(name)) { serializer.getContext().setDisableIntegerConversion(true); } return true; } }; // 在配置中添加过滤器 fastJsonConfig.setSerializeFilters(filter); // 修改自定义序列化器 public void write(JSONSerializer serializer, ...) { Boolean disable = (Boolean) serializer.getContext().getExtra("disableIntegerConversion"); if (Boolean.TRUE.equals(disable)) { defaultSerializer.write(...); } else { // 转换逻辑 } } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值