shiro框架提供了rememberme这个功能,用户登陆后会生成经过加密并编码的cookie,在服务端接收cookie值后,进行解密流程
Base64解码——>AES解码——>反序列化
因此我们只需要获取AES加密的密钥,就可以构造一个恶意对象,再对其进行加密
反序列化——>AES加密——>Base64编码
然后将构造的恶意对象,将其作为cookie的rememberMe字段发送,Shiro将rememberMe进行解密并且反序列化,最终造成反系列化漏洞。
漏洞成因:
Shiro 1.2.4及之前的版本中,AES加密的密钥默认硬编码在代码里(SHIRO-550)
源码分析:
入口点:
onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info)函数
isRememberMe(token) 判断token是否为true
token为true后,进入rememberIdentity函数
跟入getIdentityToRemember函数,这个函数接收subject和AuthenticationInfo作为参数,但是忽略了subject参数,只返回AuthenticationInfo中的principals,这个函数主要用来存储用户主体,相当于把获取的用户名赋值给principals
跟入rememberIdentity函数,看到调用了convertPrincipalsToBytes函数
继续跟入convertPrincipalsToBytes函数,发现这个函数先对之前获取的用户进行反序列化,利用getCipherService()来判断返回的结果是否为空(如果AbstractRememberMeManager类中有一个CipherService属性,那么getCipherService方法就会返回这个属性的值。如果没有这个属性,那么getCipherService方法就会返回null),后进入encrypt函数
encrypt函数
定义一个变量value,初始化为参数serialized。 获取一个CipherService对象,赋值给变量cipherService。 如果cipherService不为空,执行以下操作: 调用cipherService的encrypt方法,传入参数serialized和一个加密密钥,得到一个ByteSource对象,赋值给变量byteSource。 调用byteSource的getBytes方法,得到一个字节数组,赋值给变量value。 返回变量value。
跟入这个函数
cipherService.encrypt(serialized, getEncryptionCipherKey())函数
cipherService是一个接口,encrypt是接口中的函数
在CipherService.java中发现了AesCipherService
继续跟入发现了AES,可以看出是AES加密
继续跟入继承的DefaultBlockCipherService,发现了其运行方式为CBC与填充方案是 PKCS5
跟入PKCS5,可以看出整个加密方法是AES-CBC-PKCS5Padding
回到之前的encrypt函数,这次的目标是getEncryptionCipherKey()函数
跟入getEncryptionCipherKey()函数
查看encryptionCipherKey的调用层次,会发现调用了DEFAULT_CIPHER_KEY_BYTES这个常量
查看一下这个常量,发现是由base64的硬编码,开头到这一步刚好可以分析出加解密为
反序列化——>AES-CBC-PKCS5Padding——>base64
在调用关系中可以看出,这个AbstractRememberMeManager()是继承CookieRememberMeManager,跟入后看到了熟悉的"rememberMe"
总结流程:
-
用户名序列化
-
AES-CBC加密,key已知为
kPH+bIxk5D2deZiIxcaaaA==
-
Base64编码
-
将上述设置到cookie中的rememberme字段
提示:不怎么会java,文章可能有些错误