如何利用Padding Oracle复现Shiro RCE

17 人参与

在一次内部渗透演练中,团队偶然发现 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,改用 Jdk7u21Spring1 等链路。

把所有环节串联起来后,只需一次 Cookie 替换,就能在目标服务器的 /tmp 目录看到新建的 pwned 文件。实验结束的那一刻,我在终端里敲下 ls /tmp/pwned,看到文件名闪烁的瞬间,心里不免有点小激动。

参与讨论

17 条评论
  • 夜影枭

    默认密钥居然还能用,管理员心真大😂

    回复
  • 生活日记

    求问爆破时怎么判断响应差异?光看状态码不够吧

    回复
  • 山隐客

    之前搞过Shiro漏洞,确实折腾了好久

    回复
  • 软萌棉花糖

    太硬核了,看完还是不敢动手

    回复
  • 雷神之子

    又是CBC padding的问题,Shiro咋还不修

    回复
  • 刻春秋

    那个30ms间隔是实测出来的吗?

    回复
  • 灯笼匠曹

    ls /tmp/pwned那一刻我也想试试hhh

    回复
  • 枕霞仙子

    感觉步骤漏了点啥,ysoserial生成的payload怎么塞进去?

    回复
  • 邓三十

    老版本Tomcat+Shiro组合现在还有人用?

    回复
  • 素衣清浅

    别光说成功,失败几次才成的?

    回复
  • 古卷香

    看不懂但大受震撼🤔

    回复
  • 疯狂的火焰

    这玩意儿真能复现?我上次试了没跑通

    回复
  • 怀旧拾贝

    CommonsCollections1在JDK8u292还能用不?

    回复
  • 黑曜使者

    这操作要是被WAF拦了咋办?

    回复
  • 雾岛眠

    实战写得很细,好评

    回复
    1. 梦之驿站

      @ 雾岛眠 同感,细节给的很足

      回复
  • 泡泡小精灵

    密钥那部分挺关键,容易踩坑

    回复