Skip to content

Latest commit

 

History

History
124 lines (95 loc) · 11.6 KB

File metadata and controls

124 lines (95 loc) · 11.6 KB

Fastjson反序列化链(Fastjson)

  • 类别javanative 别名:— 优先级:15
  • 作者:Y4tacker、1ue
  • 依赖com.alibaba:fastjson:1.x(目标 classpath 存在 fastjson 1.x 即可;无需开启 AutoType)

作用

Java 原生反序列化 入口下,借助目标环境已存在的 fastjson 1.x,把内层「带危险 getter 的对象」放进 com.alibaba.fastjson.JSONArray,利用 JSONArray.toString() 会对元素执行 JSON 序列化、从而遍历调用其所有 getter 的特性,把 Java 反序列化的 toString 触发点转换成「任意 getter 调用」跳板。是一条不依赖 CommonsCollections / BeanUtils、仅需 fastjson 在 classpath 上的 Getter 型跳板。

其定位与 CommonsBeanutils1 类似(都把入口能力收敛为「调用内层对象的 getter」),区别在于触发媒介是 JSONArray.toString() 而非 BeanComparator.compare()

链接标签

  • 入口 tagsJavaNativeDeserialize —— 承接 Java 原生反序列化入口(ObjectInputStream.readObject)。
  • 衔接 nextTagsTemplatesImplChainTemplatesImplWrapperChainLdapAttributeChainSignedObjectChainMapMessageChainC3p0JndiChainDataSourceChainsDataSourceWrapperChainhutoolJndiDSFactoryhutoolPooledDSFactoryhutoolSimpleDSFactory —— 之后可接任意「通过 getter 触发」的 sink 链:字节码型(TemplatesImpl 系)走 getOutputProperties,JNDI 型(LdapAttribute、C3p0、各类 DataSource/DBCP、hutool 连接池)走 getter 中的 JNDI lookup。
  • excludesNotForFastjson —— 标记了该 tag 的 gadget(自身即依赖 fastjson 处理逻辑、或与本链的引用绕过冲突者)不能与本链共用。

源码剖析

全部逻辑集中在 getObjectinvoke 两个方法,toString 字段(@Param)在两条构造路径之间切换。

invoke:取内层对象并向上下文登记

@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
    Object object = chain.doCreate(context);                    // 取链中下一环(内层 getter 载体)
    context.put(ContextTag.FASTJSON_HANDLE_BYPASS_KEY, object); // 把该对象登记到上下文
    return this.getObject(object);
}
  • 按「由内向外」构建:chain.doCreate(context) 先产出内层对象 object(即下游 sink 链的最外层产物,一个「访问某 getter 即触发」的对象,如 TemplatesImpl、JdbcRowSetImpl、各 DataSource)。
  • context.put(ContextTag.FASTJSON_HANDLE_BYPASS_KEY, object):把该 getter 载体登记进 GadgetContext。同一 key 被大量 equals / toString 型 gadget 读取(grep 可见 HashMapEqualsHashTableEqualsRdn$RdnEntryEqualsBadAttributeValueExpExceptionToString 等均 context.get(FASTJSON_HANDLE_BYPASS_KEY)),用于跨 gadget 复用「同一个 getter 触发对象」,避免重复构造并保证引用一致。
  • 返回 getObject(object) 得到最终交给上层 Payload 序列化的外层对象。

getObject 默认路径(toString = "badAttributeValueExpException"

public Object getObject(Object obj) throws Exception {
    JSONArray jsonArray = new JSONArray();
    jsonArray.add(obj);                                 // 关键:JSONArray 元素 = 内层 getter 载体
    // ... 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);         // 外层 HashMap,key=obj,value=bave
    return map;
}

逐段解释(触发链 BadAttributeValueExpException.readObject() => JSONArray.toString() => getter):

  1. new JSONArray(); jsonArray.add(obj):把内层 getter 载体作为 JSONArray 唯一元素。JSONArray.toString() 内部走 JSON.toJSONString(this),fastjson 序列化器会对元素的每个属性调用其 getXxx()(ASM/反射 getter 枚举)——这正是「任意 getter」触发点。
  2. 通过反射把 jsonArray 塞进 javax.management.BadAttributeValueExpException 的私有字段 val。JDK 中 BadAttributeValueExpException.readObject()System.getSecurityManager() == null(绝大多数部署默认)时会执行 valObj.toString(),从而在原生反序列化阶段调用 jsonArray.toString(),把控制流交给上一步的 fastjson getter 枚举。
  3. 清空 stackTrace / suppressedExceptions / cause:使反序列化过程更干净、缩小 payload 并规避某些字段写入异常。
  4. 最外层再包一个 HashMapmap.put(obj, bave):以内层对象 obj 为 key、BadAttributeValueExpException 为 value。外层 HashMap 承担 description 所称「Hashmap 的引用机制绕过 Fastjson 反序列化的检测」——将 obj 同时作为 key 与 JSONArray 元素两处引用出现,使 fastjson 在序列化枚举时对 obj 的 getter 真正求值(而非被循环引用 $ref/去重逻辑短路),保证 sink getter 被执行。

getObject 的 xString 路径(toString = "xString"

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]);
    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);   // 哈希碰撞双键 HashMap
    ArrayList<Object> arrayList = new ArrayList<>();
    arrayList.add(obj);
    arrayList.add(map);
    return arrayList;
}
  • XStringForCharscom.sun.org.apache.xpath.internal.objects.XString 家族)替代 BadAttributeValueExpException 作为 toString 触发媒介。createWithoutConstructor 免构造实例化、m_obj 置空字符数组,避免构造/序列化副作用。
  • "zZ""yy" 是一对 fastjson/JDK 场景常用的同哈希桶碰撞键名;两个 HashMap 中 xStringjsonArray 的键位互换,再由 PayloadHelper.makeMap(hashMap1, hashMap2) 组合成一个「readObject 时会强制两键 equals 比较」的 HashMap(与本项目 XString1makeMap(h1, h2) 同一套哈希碰撞原语)。反序列化 HashMap.readObject → putVal 时触发 XString.equals(jsonArray)XString#equals 会对参数取 toString,从而落到 JSONArray.toString => getter
  • 选择 xString 的动机见 @Param.descriptionxString 在高版本 JDK 上 serialVersionUID 一致,跨 JDK 兼容性优于 BadAttributeValueExpException(后者在部分高 JDK 上 SerId/字段有差异,易 InvalidClassException)。最外层用 ArrayList[obj, map],同样让 obj 与内部 jsonArray 中的 obj 形成两处引用,配合上文「引用机制」保证 getter 求值。

注:PayloadHelper.makeMap / Reflections.* 属工具类(打包在依赖 jar 中,未随本类反编译出体),其语义依据同项目内 XString1 等对 makeMap(h1, h2) 的一致用法确定——即经典的「两元素哈希碰撞 HashMap,readObject 阶段触发 key1.equals(key2)」原语(与 ysoserial URLDNS 同源)。

参数(@Param)

字段 名称 说明 类型 可选值 默认
toString 选用toString进行触发 选择 badAttributeValueExpExceptionxString 作为 toString() 触发媒介;xString 在高 JDK 上 serialVersionUID 一致、兼容性更好 ParamType.Choice badAttributeValueExpExceptionxString badAttributeValueExpException

适用版本与原理要点

  • 依赖:目标 classpath 存在 com.alibaba:fastjson:1.x。本链利用的是 JSONArray.toString()(序列化侧枚举 getter)而 fastjson 的反序列化 AutoType,因此不要求开启 AutoType、不受 AutoType 黑白名单约束——只要 fastjson 类在类路径即可。
  • 入口:Java 原生反序列化(readObject)。链尾 getter 载体决定最终效果:TemplatesImpl 系 → 加载字节码 RCE(要求目标 JDK 能加载 TemplatesImpl,通常 JDK < 17);LdapAttribute / C3p0 / DataSource / hutool 连接池 → JNDI 注入或 JDBC 二次利用。
  • 两种 toString 媒介badAttributeValueExpException 依赖 System.getSecurityManager()==null(默认成立)且 JDK 版本与 BadAttributeValueExpException 序列化兼容;xStringHashMap#readObject → XString#equals 碰撞路径,高 JDK 兼容性更好,是高版本环境的推荐选项。
  • 引用绕过要点:外层容器(默认 HashMap,xString 下 ArrayList + 碰撞 HashMap)刻意让内层 objJSONArray 内元素形成多处引用,规避 fastjson 序列化去重 / 循环引用短路,确保 sink getter 实际被求值——即 description 的「Hashmap 引用机制绕过检测」。

所属预设链

关联

  • 下游(nextTagsTemplatesImplgetOutputProperties 字节码 sink)、LdapAttribute、C3p0 / 各 DataSource / hutool 连接池(JNDI getter sink)、SignedObject / MapMessage 等二次反序列化 getter。
  • 同族Fastjson2(同一 getObject 思路,依赖收敛为 com.alibaba:fastjson <= 2.0.26)。
  • 共享上下文机制:与 equals / tostring 型 gadget 共用 ContextTag.FASTJSON_HANDLE_BYPASS_KEY(如 HashMapEqualsBadAttributeValueExpExceptionToString)。
  • 同类思路对照CommonsBeanutils1(Getter 跳板,媒介为 BeanComparator.compare)。

防御与检测

  • 检测特征
    • 序列化流中同时出现 javax.management.BadAttributeValueExpException + com.alibaba.fastjson.JSONArray(默认路径),或 com.sun.org.apache.xpath.internal.objects.XStringForChars + 碰撞键名 zZ/yy + java.util.HashMap(xString 路径)。
    • JSONArray 出现在 readObject 可达的字段(BadAttributeValueExpException.val)中,是强烈信号。
    • 反序列化异常栈中出现 JSONArray.toString → JSON.toJSONString → ... getXxx、或 HashMap.readObject → XString.equals 帧。
  • 加固建议
    • 反序列化入口启用类白名单(ObjectInputFilter / ValidatingObjectInputStream),拒绝 BadAttributeValueExpExceptionXStringForCharsJSONArray 等非业务类。
    • 生产环境尽量移除不必要的 fastjson 1.x 依赖;确需保留时升级并配合安全模式(本链虽不走 AutoType,但移除依赖可根除该跳板)。
    • 高 JDK(≥ 17)默认无法加载 TemplatesImpl 字节码路径,可显著削弱 RCE sink;同时对 JNDI 相关(com.sun.jndi.ldap.object.trustURLCodebase 等)做限制以阻断 JNDI sink。