AspectJWeaver反序列化分析
上周末参加了TSCTF,有两道Java题目,一道是RMI的,lookup的时候用hook传Interface自带的恶意类参数,然后使用方法之前hook住Server端的ip,将0.0.0.0改成正确ip,即可使用。其中由于传的shell是固定格式,而且使用的是dash,构造比较恶心,就不在这讨论了。
另一道是java反序列化的题目。代码很简单,解读起来就是post一个String,然后使用base64解密,在通过serialkiller过滤,最后反序列化。
那么思路也很清晰,就是构造可以通过过滤的payload,然后讲序列化的字节数组进行base64加密,得到payload。但是serialkiller过滤了CC1-CC6常见的方法。
赛后看到参加MRCTF师傅的wp说可以使用AspectJWeaver,然后用org.apache.commons.collections.functors.MapTransformer替代ConstantTransformer就可以了。比赛时候其实我也找了AspectJWeaver反序列化利用的poc,但是看里面有ConstantTransformer就想着不可行,比赛的时候一直想找一个可以直接用的poc,还是太年轻、太菜了。
所以今天先来学习一下AspectJWeaver
什么是AspectJWeaver
AspectJ 是 Eclipse 基金组织的开源项目,它是 Java 语言的一个 AOP 实现,是最早、功能比较强大的 AOP 实现之一,对整套 AOP 机制都有较好的实现,很多其他语言的 AOP 实现也借鉴或者采纳了 AspectJ 中的很多设计。在 Java 领域,AspectJ 中的很多语法结构基本上已经成为 AOP 领域的标准。本条链是一条任意文件写入的利用链,可以将内存马写入进去,这也避免了不出网的环境下的窘境。
前置知识
SimpleCache$StoreableCachingMap
在 org.aspectj.weaver.tools.cache.SimpleCache 类中定义了一个内部类 StoreableCachingMap,这个类继承了 HashMap,提供了将 Map 中值写入文件中的功能。
在调用 put 方法向 StoreableCachingMap 中放入值时,会调用 writeToPath 方法将 value 中的值写入到文件中。

可以看到,路径由 folder 起始,拼接 File.separator 以及 key ,values 是 byte 数组类型的数据。
由此,可构建:实例化一个StoreableCachingMap,传入参数folder和storingTimer,folder作为路径,然后某方法调用StoreableCachingMap的put,传入两个Object,其中key是文件名,value是文件内容。这就构成了一个文件写出的 sink 点。
接下来就看谁调用了put方法。
LazyMap
在 CC链中的 org.apache.commons.collections.map.LazyMap 类,我们通过触发其 get 方法来触发 Transformer 的 transform 方法来触发 ChainedTransformer/InvokerTransformer 等后续调用链。
实际上 LazyMap 在 transform 之后会调用封装的内部 map 的 put 方法将结果保存,这就触发了 put 方法。
POC构造
ConstantTransformer
ysoserial 此链中使用了 HashSet 反序列化来触发 TiedMapEntry 的 hashCode 进一步触发 LazyMap 的 get 方法,在 CC6/CC7 中使用 HashSet/HashMap/Hashtable 触发本质上都是一样的。这里使用 HashSet 来触发。
1 | public class AspectJWeaver { |
注意:以get形式传参时要Base64后的字符串进行url编码
1 | java.net.URLEncoder.encode(str, "UTF-8") |
Gadget如下:
1 | HashSet#readObject() |
依赖版本
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 | protected static byte[] getBytescode() throws Exception { |
并修改代码
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 | Accept: text/plain, */*; q=0.01 |
具体细节请见大佬的博客和github仓库。
打内存马
也有说直接打内存马,但还没尝试
