URLDNS链分析
URLDNS
分析
URLDNS主要用到了两个数据结构:HashMap和URL至于为什么是这两个,我们看看他们俩各有什么特性吧~
HashMap
readObject-反序列化的入口
反序列化一个对象时,Java的
ObjectInputStream
会调用被反序列化对象的readObject
方法,以便读取对象的状态并恢复它的字段。在URLDNS中使用了HashMap.readObject()方法java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// HashMap.readObject
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new InvalidObjectException("Illegal load factor: " + loadFactor);
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) { // (if zero, use defaults)
...
// 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); //<-------flag: 在这里会调用hash(key)----------
}
}
}跟进putVal(hash(key), key, value, false, false); 方法
java1
2
3
4
5//HashMap.hash
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//<-------flag: 如果key不为空,则调用key.hashCode()方法
}
到这里我们可以看到 HashMap在执行反序列化过程中会循环调用key的hashCode()方法,
URL
我们给Key一个URL类型的对象就会调用URL.hashCode()方法啦
java1
2
3
4
5
6
7
8// URL.hashCode()
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);//<--------flag: hashCode==-1时会调用handler.hashCode(this)-----
return hashCode;
}点开后发现handler是一个URLStreamHandler类型的对象
跟进看一下URLStreamHandler.hashCode(URL)方法
java1
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);//<----flag: 看方法名(获取主机的地址)----
...
}再跟进getHostAddress(u)
java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18protected synchronized InetAddress getHostAddress(URL u) {
if (u.hostAddress != null)
return u.hostAddress;
String host = u.getHost();
if (host == null || host.equals("")) {
return null;
} else {
try {
u.hostAddress = InetAddress.getByName(host);//<------flag: InetAddress.getByName(host)是根据主机名获取地址,很明显就是DNS请求了------------
} catch (UnknownHostException ex) {
return null;
} catch (SecurityException se) {
return null;
}
}
return u.hostAddress;
}
调用链
至此,可以分析URLDNS调用链为
java
1 | HashMap.readObject //反序列化入口 |
构造反序列的对象
根据上面的信息我们可以推断出,我们序列化的对象需要满足以下条件:
- Object类型为HashMap
- HashMap的Key是URL类型
- (URL)key的hashCode成员为-1(由于URL.hashCode为私有成员,所以需要使用反射调用)
java
1 | void urlDns() throws Exception { |
这样写会有问题吗?有!!!!
回到URL的hashCode方法,有个if (hashCode != -1)
,可以测试一下
java
1 | void urlDns() throws Exception { |
为什么url.hashCode的值变了?
跟进hashMap.put()看一下
java
1 | // hashMap.put() |
根据前面分析的 hash(url)会调用 url.hashCode() 由于此时hashCode==-1,所以会发送DNS请求并修改hashCode的值,所导致的现象是序列化的时候发送了DNS请求,而反序列化的时候不会发送DNS请求
所以,如果要实现序列化时不调用,反序列化时才调用,则应该在put前将hashCode改为除-1的任何值,在put进hashMap之后再改为-1
完整源码
java
1 | public class Gadget{ |