URLDNS链反序列化分析

白

写这篇文章,一来是记录一下自己的学习过程,同时也想以我自认为比较好理解的方式和逻辑为大家分析这个链

简介

URLDNS链只依赖原生类,没有 jdk 版本限制,它不会执行命令,只会触发 DNS 解析,因此通常用来探测是否存在反序列化漏洞。这个漏洞关键点是 Java 内置的 java.net.URL 类,这个类的 equals 和 hashCode 方法在对 URL 对象进行比较时(使用 equals 方法或hashCode 方法),会触发一次 DNS 解析,因为对于 URL 来说,如果两个主机名(host)都可以解析为相同的 IP 地址,则这两个主机会被认为是相同的。

分析

URL

先分析一下URL类的equals 和 hashCode 方法为什么能触发DNS请求

先写下面一段代码

1
2
3
4
URL url1 = new URL("http://1.dnslog.cn");
URL url2 = new URL("http://2.dnslog.cn");
url1.equals(url2);
url1.hashCode();

equals

跟进equals方法,如下图

1

可以看到,URL#equals 方法重写了 Object 的判断,调用 URLStreamHandler#equals 方法进行判断,跟进。

2

URLStreamHandler#equals 方法判断 URL 对象的锚点是否相同,并调用 sameFile 方法比较两个 URL,看它们是否引用了相同的 protocol、host、port和path,跟进sameFile

3

sameFile 方法在比较 host(主机)时,调用 hostsEqual 方法进行比较,跟进hostEqual方法

4

可以看到,hostsEqual 方法调用 getHostAddress 方法,对要比较的两个 URL 进行请求解析 IP 地址,并实施对比。跟进getHostAddress方法

5

没啥说的,接着跟

6

URL 类的 getHostAddress() 方法检查 hostAddress 是否已经解析,如果没有,则使用 getByName(host) 来解析主机名,触发了 DNS 请求

hashCode

还是跟进上面那个代码的hashCode,如下图

7

此方法将一个对象映射为一个整型的值,通常与 equals 方法同时出现。当 equals 方法被重写时, hashCode 也需要被重写。按照一般 hashCode 方法的实现来说,如果两个对象通过 equals 方法判断相同,那它们的 hashCode 一定相等。URL 的 hashCode 方法也进行了重写,调用了 URLStreamHandler#hashCode 方法。在此之前有一个判断,那就是 hashCode != -1。(PS:hashCode默认就是-1,在构造函数里可以看到),此处不深究这个判断条件,后面会分析。当判断走到hashCode方法,跟进去

8

也是调用了 getHostAddress 方法对 URL 的 host 进行解析。

HashMap

前面提到这个漏洞的关键点是java.net.URL 类的 equals 方法或 hashCode 方法,用于触发DNS请求,但是URL的readObject方法并没有调用到hashCode方法

9

所以我们需要找到另一个重写了readObject方法并且调用了URL类的hashCode方法的类,所以找到了HashMap这个类,如下图

10

这里调用了hash方法,跟进

11

当key不为null,将调用key的hashCode方法。如果这里我的key就是一个url对象,那不就是调用url的hashCode方法吗?

到这里就很清晰了,HashMap就是入口点(kick-off),URL的equals和hashCode是触发点(sink)

但是

其实还有问题:第一,在使用 HashMap 的 put 方法时,也是调用 putVal 方法(图10),会对 key 进行 hash,触发解析。如果我们不想在生成 payload 时触发 DNS 解析,就要使用反射将值放进去。(感觉无所谓)

第二(主要原因), URL 对象有一个属性 hashCode,默认是 -1,使用hashCode 方法计算时会在 hashCode 属性中缓存已经计算过的值,如果再次计算将直接返回值,不会在触发 URLStreamHandler 的 hashCode 方法,也就不会触发漏洞。所以我们需要在生成的 HashMap 中的URL 参数的 hashCode 值在反序列化时为 -1,而刚才说过,如果使用 put 方法,会调用一次 key 的 hash 计算,也就是 URL 的hashCode 方法,这样就把 hashCode 缓存了,在反序列化时就不会触发 URLStreamHandler 的 hashCode 方法以及后面的逻辑。所以有两种思路解决这个问题:

  • 如果使用 HashMap 的 put 方法,将 URL 对象放入 Map 的 key 中之前,先将其 URL 对象 hashCode 进行修改,使其不等于 -1,这样就不会触发 DNS 查询,放入之后,再使用反射将 URL 对象中的 hashCode 修改为 -1,反序列化的时候就可以正常触发了。
  • 直接反射调用 HashMap 的 putVal 方法绕过 hash 计算。(由于 JDK 1.7 中方法名不一样,细节也不一样,所以不具有通用性)

第一种思路的代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class URLDNS {

public static void main(String[] args) throws Exception {

HashMap<URL, Integer> hashMap = new HashMap<>();
URL url = new URL("http://Wandering.dnslog.cn");
Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
f.setAccessible(true);

f.set(url, 0x01010101);
hashMap.put(url, 0);
f.set(url, -1);

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("urldns.bin"));
oos.writeObject(hashMap);
//ObjectInputStream ois = new ObjectInputStream(new FileInputStream("urldns.bin"));
//ois.readObject();
}
}

第二种方法就不介绍了,详见su18师傅的文章,还有对ysoserial链的分析

参考

https://su18.org/post/ysoserial-su18-1/#%E5%9B%9B-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-gadget-%E5%88%86%E6%9E%90

https://www.bilibili.com/video/BV16h411z7o9?p=1&vd_source=9fe5dd16d67801322fd30df51f7b3fd8


URLDNS链反序列化分析
http://example.com/2024/09/03/URLDNS/
作者
想有双手
发布于
2024年9月3日
许可协议