Java反序列化之反射机制

Java (2) 2024-04-28 13:23

Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说Java反序列化之反射机制,希望能够帮助你!!!。

目录

  • 前言
  • Java反射机制
  • 反射机制方法
  • 指定构造方法生成实例
  • 执行私有方法
  • 总结

前言

每次听到大佬在讲或者看论坛等一些方式学java反序列化漏洞时,都会有一个词叫做反射机制,大佬顺势借着这个词,就给你造出一个payload,对于刚学java反序列化的我们,可能有点会懵圈,反正我是懵了,所以就赶紧学了一波,不然和大佬差距越来越大。所以这篇文章主要讲述java反射机制

Java反射机制

Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。

我不太擅长文字表达,还是上图操作把

不用反射机制的例子


//定义一个animals接口
interface animals { 
   
    public abstract void print();
}
//定义类来实现animals接口的抽象方法
class Dog implements animals { 
   
    public void print() { 
   
        System.out.println("Dog");
    }
}

class Cat implements animals { 
   
    public void print() { 
   
        System.out.println("Cat");
    }
}

// 构造一个zoo类
// 之后如果我们在添加其他的实例的时候只需要修改zoo类
class zoo { 
   

    public static animals getInstance(String animalsName) { 
   
        animals a = null;
        if ("Dog".equals(animalsName)) { 
   
            a = new Dog();
        }
        if ("Cat".equals(animalsName)) { 
   
            a = new Cat();
        }
        return a;
    }
}

public class reflection { 
   
    public static void main(String[] args) { 
   
        //借助zoo类寻找对应的类来实现接口
        animals a=zoo.getInstance("Cat");
        if(a!=null)
            a.print();
    }
}

这时候添加动物,只需要

  • 添加类
  • 修改zoo
  • 修改main函数的动物类

把上面修改为反射机制

//定义一个animals接口
interface animals { 
   
    public abstract void print();
}

//定义类来实现animals接口的抽象方法
class Dog implements animals { 
   
    public void print() { 
   
        System.out.println("Dog");
    }
}

class Cat implements animals { 
   
    public void print() { 
   
        System.out.println("Cat");
    }
}

// 构造一个zoo类
// 之后如果我们在添加其他的实例的时候只需要修改zoo类
class zoo { 
   

    public static animals getInstance(String className) { 
   
        animals a = null;
        try { 
   
            //借助Class.forName寻找类名,并用newInstance实例化类似于new
            a = (animals) Class.forName(className).newInstance();
        } catch (Exception e) { 
   
            e.printStackTrace();
        }
        return a;
    }
}

public class reflection { 
   
    public static void main(String[] args) { 
   
        //借助zoo类寻找对应的类来实现接口(classname为当前包名加类名)
        animals a = zoo.getInstance("com.cc1.Dog");
        if (a != null)
            a.print();
    }
}

这时候添加动物只需要

  • 添加类
  • 修改main函数的动物类

省了一步,传入类名可控,发现好像是存在的类都可以调

反射机制方法

我们用的最多的可能是

  • forName(调用类)
  • getMethod(调用类下方法)
  • invoke(执行)
  • newInstance(实例化对象)

Class.forName(className).getMethod(methodName).invoke(Class.forName(className).newInstance());

下面我们用反射机制来弹出计算机(calc)或者记事本(notepad)

由于弹出计算机有点多这次我就弹记事本把,总而言之,能弹出来就很美妙

Runtime.getRuntime().exec("notepad");

Java反序列化之反射机制_https://bianchenghao6.com/blog_Java_第1张
我们看下getRuntime函数
Java反序列化之反射机制_https://bianchenghao6.com/blog_Java_第2张
得知,该函数是Runtime类获取对象的方式,个人感觉是每用一次就调一次比较麻烦,为了不调用一次建立一个对象所以封装成了函数

类对象获取方式

  • Class.forName(类名获取)
  • zoo.class(已经加载过的类)
  • obj.class(实例)
    Java反序列化之反射机制_https://bianchenghao6.com/blog_Java_第3张

类初始化

修改zoo类,增加初始块、静态初始块、和构造函数


class zoo { 
   
    //初始块
    { 
   
        System.out.println("1 " + this.getClass());
    }

    //静态初始块
    static { 
   
        System.out.println("2 " + zoo.class);
    }

    public zoo() { 
   
        System.out.println("3 " + this.getClass());
    }

    public static animals getInstance(String className) { 
   
        animals a = null;
        try { 
   
            //借助Class.forName寻找类名,并用newInstance实例化类似于new
            a = (animals) Class.forName(className).newInstance();
        } catch (Exception e) { 
   
            e.printStackTrace();
        }
        return a;
    }
}

类初始化执行顺序:静态初始块
Java反序列化之反射机制_https://bianchenghao6.com/blog_Java_第4张
类实例化执行顺序:静态初始块 - > 初始块 - > 构造函数
Java反序列化之反射机制_https://bianchenghao6.com/blog_Java_第5张
由此得知,类初始化和类实例化不一样

接下来增加zoo1类继承zoo类


class zoo1 extends zoo{ 
   
    //初始块
    { 
   
        System.out.println("11 " + this.getClass());
    }

    //静态初始块
    static { 
   
        System.out.println("12 " + zoo.class);
    }

    public zoo1() { 
   
        System.out.println("13 " + this.getClass());
    }
}

子类初始化顺序:父类静态初始化块 - > 子类静态初始化块
Java反序列化之反射机制_https://bianchenghao6.com/blog_Java_第6张
子类实例化顺序:父类静态初始化块 - > 子类静态初始化块 - > 父类初始化块 - > 父类构造函数 - > 子类初始化块 - >子类构造函数
Java反序列化之反射机制_https://bianchenghao6.com/blog_Java_第7张
以上可以得知,当使用Class.forName时,且类静态初始化块可控,可以执行任意代码

调用内部类

Class.forName(“java.lang.Runtime”)来获取类(java.lang.Runtime是Runtime类的完整路径)

getMethod

getMethod 的作用是通过反射获取类的某个特定的公有方法。
java支持类重载,但不能仅通过一个函数名确定一个函数,所以在调用getMethod时,需要传给它方法的参数类型列表
Class.forName(“java.lang.Runtime”).getMethod(“exec”, String.class)

invoke

静态和动态方法的区别
Java反序列化之反射机制_https://bianchenghao6.com/blog_Java_第8张

invoke方法在getMethod类下,作用时传递参数,执行方法
public Object invoke(Object obj, Object… args)
第一个参数是getMethod获取的方法的类对象(如果方法是静态方法则传类)
获取exec函数的类对象
Class.forName(“java.lang.Runtime”).getMethod(“getRuntime”).invoke(Class.forName(“java.lang.Runtime”))
由于getRuntime是静态方法,所以传类
invoke(Class.forName(“java.lang.Runtime”).getMethod(“getRuntime”).invoke(Class.forName(“java.lang.Runtime”)),“calc.exe”)

最后我们合并一下


Class.forName("java.lang.Runtime").
                getMethod("exec", String.class).
                invoke(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")), "notepad");
                

Java反序列化之反射机制_https://bianchenghao6.com/blog_Java_第9张

指定构造方法生成实例


String str="notepad";
ProcessBuilder pb = new ProcessBuilder(str);
pb.start();

getConsturctor(函数可以选定指定接口格式的构造函数(由于构造函数也可以根据参数来进行重载)
选定后我们可以通过newInstance(),并传入构造函数的参数执行构造函数

ProcessBuilder类有两个构造函数

  • public ProcessBuilder(String… command)(String…变长的字符串数组String[].class)
  • public ProcessBuilder(List command)

分别使用构造方法

  • Class.forName(“java.lang.ProcessBuilder”).getConstructor(String[].class).newInstance(new String[][]{
    {“notepad”}})
  • Class.forName(“java.lang.ProcessBuilder”).getConstructor(List.class).newInstance(Arrays.asList(“notepad”))

执行完构造方法实例后,在进行强制转化使用start函数即可

( (ProcessBuilder) Class.forName(“java.lang.ProcessBuilder”).getConstructor(List.class).newInstance(Arrays.asList(“notepad”))).start();

实际中,肯定用不了,哪有这么好的事,还是接着反射把

Class.forName(“java.lang.ProcessBuilder”).getMethod(“start”).invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList(“notepad”)));
Java反序列化之反射机制_https://bianchenghao6.com/blog_Java_第10张
这里可能有人会好奇我写的里那的另一个构造函数,String…command这个传参为什么用new String[][]{
{“notepad”}},不应该是new String[]{“notepad”},现在用应该的

((ProcessBuilder) Class.forName(“java.lang.ProcessBuilder”).getConstructor(String[].class).newInstance(new String[]{“notepad”})).start();

在这行打断点调试
Java反序列化之反射机制_https://bianchenghao6.com/blog_Java_第11张
我们传的是一个字符串数组到了实例化的时候变成了一个字符串,再看看另一个构造函数(List)

( (ProcessBuilder) Class.forName(“java.lang.ProcessBuilder”).getConstructor(List.class).newInstance(Arrays.asList(“notepad”))).start();

依旧还是这行打断点

Java反序列化之反射机制_https://bianchenghao6.com/blog_Java_第12张
由此可知,List传入时会被当作Object的第一项,而String[]会被当做Object,所以多加一层[]{}

执行私有方法

通过函数getDeclaredConstructor获取私有方法,再利用setAccessible(true)打破私有方法限制


Class cls = Class.forName("java.lang.Runtime"); 
Constructor m = cls.getDeclaredConstructor(); 
m.setAccessible(true); 
cls.getMethod("exec", String.class).invoke(m.newInstance(), "notepad");


总结

这里也是通过反射机制调用任意类,大致让我这个门外汉也明白了反射,看到这里的你们想比应该也明白了,如果有什么疑问,我们可以一起交流学习,也请大佬多多指点

大家可以关注菜鸡的公众号,有什么好想法也可以让我学习一下,有什么问题可以一块解决,由于二维码违规,下面是base64编码的文字

5b6u5L+h5YWs5LyX5Y+34oCc5a6J5YWo5re35a2Q4oCd77yM5Y+v5Lul55So5b6u5L+h5pCc5LiA5pCc77yM5q2j5Zyo5a6M5ZaE5LitLi4uLi4u

今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

发表回复