如何利用Padding Oracle复现Shiro RCE
Apache Shiro 远程代码执行漏洞复现
在一次内部渗透演练中,团队偶然发现 Shiro 的 rememberMe Cookie 竟能被当作加密的 Padding Oracle 入口,进而触发反序列化链执行系统命令。回想起当时的调试画面:抓包工具里那枚被篡改的 Base64 字符串,配合手工填充的十六进制块,最终让 Tomcat 的 servlet 抛出异常,却悄悄把恶意对象写回了客户端。把这段经历拆解成几段文字,或许能给后续的安全研究者一点参考。
攻击原理概览
Shiro 在处理 rememberMe 时,会把用户对象序列化后使用 AES‑128‑CBC 加密,再把密文放进 Cookie。CBC 模式在解密时会检查填充合法性,如果填充值不符合 PKCS#7 标准,框架会抛出异常并返回 400。攻击者可以利用这个“合法性校验”作为 oracle,逐字节猜测密文的真实内容,最终恢复出完整的加密块。拿到原始明文后,只要把恶意的 Gadget 对象写进去,重新加密后发回请求,就能让 Shiro 在反序列化时触发链式执行。
环境搭建要点
实验环境选用 Ubuntu 16.04 + Tomcat 8 + Shiro 1.4.1,原因是该版本的 rememberMe 实现仍保留了旧的填充检查逻辑。部署步骤大致如下:
- 从官方仓库下载
samples-web-1.4.1.war,直接放进 Tomcat 的webapps目录。 - 启动 Tomcat,确认访问
http://localhost:8080/samples-web能看到登录页面。 - 使用
ysoserial生成 CommonsCollections1 Gadget,目标是创建/tmp/pwned文件。
Padding Oracle 实战步骤
真正的核心在于把 oracle 变成可脚本化的查询。常用的做法是利用 Burp Intruder 发送带有不同填充值的 Cookie,观察响应码是否为 400(填充错误)还是 200(填充成功)。下面的 Python 伪代码展示了最简化的字节猜测循环:
import requests, base64
def query(cipher):
cookie = 'rememberMe=' + base64.b64encode(cipher).decode()
r = requests.get('http://target:8080/', cookies={'rememberMe': cookie})
return r.status_code == 200 # True 表示填充合法
# 假设已知前 15 块,逐字节恢复第 16 块
known = b'...'
for i in range(256):
trial = known + bytes([i])
if query(trial):
print('found byte', i)
break
完整的爆破大约需要几千次请求,视网络延迟而定。实际操作时,我把请求间隔设为 30 ms,以免触发目标服务器的速率限制。爆破结束后得到的明文块被塞进原始的 rememberMe 结构里,随后使用同样的密钥(从源码中可以看到默认的 kPH+bIxk5D2deZiIxcaaaA==)重新加密,得到的新 Cookie 直接粘贴到浏览器即可触发。
常见陷阱与调试技巧
1. 密钥误判:很多人直接把默认密钥写死,实际部署时管理员往往会在 shiro.ini 里自行配置 rememberMeCipherKey,导致解密失败。解决办法是先抓一次合法登录的 Cookie,解密后查看前 16 字节是否全为 0x00,若不是则说明使用了自定义密钥,需要自行推断或暴力。
2. 异常响应判定不准:部分 Web 服务器会把填充错误和正常响应都返回 200,只是返回的页面内容不同。此时可以比对响应体的长度或关键字(如 “Invalid rememberMe cookie”)来做二次过滤。
3. Gadget 选择:CommonsCollections1 在 JDK8 以上表现稳定,但在 JDK11 里已经被部分安全厂商禁用。若目标使用更高版本的 JDK,改用 Jdk7u21 或 Spring1 等链路。
把所有环节串联起来后,只需一次 Cookie 替换,就能在目标服务器的 /tmp 目录看到新建的 pwned 文件。实验结束的那一刻,我在终端里敲下 ls /tmp/pwned,看到文件名闪烁的瞬间,心里不免有点小激动。

参与讨论
默认密钥居然还能用,管理员心真大😂
求问爆破时怎么判断响应差异?光看状态码不够吧
之前搞过Shiro漏洞,确实折腾了好久
太硬核了,看完还是不敢动手
又是CBC padding的问题,Shiro咋还不修
那个30ms间隔是实测出来的吗?
ls /tmp/pwned那一刻我也想试试hhh
感觉步骤漏了点啥,ysoserial生成的payload怎么塞进去?
老版本Tomcat+Shiro组合现在还有人用?
别光说成功,失败几次才成的?
看不懂但大受震撼🤔
这玩意儿真能复现?我上次试了没跑通
CommonsCollections1在JDK8u292还能用不?
这操作要是被WAF拦了咋办?
实战写得很细,好评
@ 雾岛眠 同感,细节给的很足
密钥那部分挺关键,容易踩坑