前言
每天一小步,进步一大步。java 反序列化学习
0x01 java反序列化利用
Apache Commons Collections <=3.2.1, <=4.0.0
1. InvokerTransformer.transform() 反射调用
在使用Apache Commons Collections库进行Gadget构造时主要利用了其Transformer接口。
1 | package org.apache.commons.collections; |
主要用于将一个对象通过 transform 方法转换为另一个对象,而在库中众多对象转换的接口中存在一个 Invoker 类型的转换接口 InvokerTransformer,并且同时还实现了 Serializable 接口。
1 | public class InvokerTransformer implements Transformer, Serializable { |
可以看到 InvokerTransformer 类中实现的transform()
接口使用 Java 反射机制获取反射对象 input 中的参数类型为 iParamTypes 的方法 iMethodName,然后使用对应参数 iArgs 调用获取的方法,并将执行结果返回。由于其实现了 Serializable 接口,因此其中的三个必要参数 iMethodName、iParamTypes 和 iArgs 都是可以通过序列化直接构造的,为命令执行创造的决定性的条件。
然后要想利用 InvokerTransformer 类中的transform()
来达到任意命令执行,还需要一个入口点,使得应用在反序列化的时候能够通过一条调用链来触发 InvokerTransformer 中的transform()
接口。
然而在 Apache Commons Collections 里确实存在这样的调用,其一是位于TransformedMap类中的checkSetValue()
方法:
1 | public class TransformedMap |
而 TransformedMap 实现了 Map 接口,而在对字典键值进行setValue()
操作时会调用valueTransformer.transform(value)
。
1 | ...省略... |
好的,现在已经找到了反射调用的上一步调用,这里为了多次进行多次反射调用,我们可以将多个 InvokerTransformer 实例级联在一起组成一个 ChainedTransformer 对象,在其调用的时候会进行一个级联transform()
调用:
1 | public class ChainedTransformer implements Transformer, Serializable { |
现在已经可以造出一个 TransformedMap 实例,在对字典键值进行 setValue() 操作时候调我们构造的 ChainedTransformer,下面给出示例代码:
1 | package com.example.demo; |
根据之前的分析,将上面这段代码编译运行后会默认会弹出计算器,对代码详细执行过程有疑惑的可以通过单步调试进行测试:
然后我们现在只是测试了使用 TransformedMap 进行任意命令执行而已,要想在 Java 应用反序列化的过程中触发该过程还需要找到一个类,它能够在反序列化调用 readObject() 的时候调用 TransformedMap 内置类 MapEntry 中的 setValue() 函数,这样才能构成一条完整的 Gadget 调用链。恰好在 sun.reflect.annotation.AnnotationInvocationHandler 类具有 Map 类型的参数,并且在 readObject() 方法中触发了上面所提到的所有条件,其源码如下:
1 | private void readObject(java.io.ObjectInputStream s) { |
可以注意到 memberValue 是 AnnotationInvocationHandler 类中类型声明为 Map<String, Object> 的成员变量,刚好和之前构造的 TransformedMap 类型相符,因此我们可以通过 Java 的反射机制动态的获取 AnnotationInvocationHandler 类,使用精心构造好的 TransformedMap 作为它的实例化参数,然后将实例化的 AnnotationInvocationHandler 进行序列化得到二进制数据,最后传递给具有相应环境的序列化数据交互接口使之触发命令执行的 Gadget,完整代码如下:
1 | package exserial.payloads; |
最终用一段调用链可以清晰的描述整个命令执行的触发过程:
1 | /* |