灌篮少年 发表于 2025-2-20 14:02:48

JAVA安全—反射机制&攻击链&类对象&成员变量方法&构造方法

前言

还是JAVA安全,哎,真的讲不完,太多啦。
本日主要是讲一下JAVA中的反射机制,因为反序列化的使用根本都是要用到这个反射机制,还有一些攻击链条的构造,也会用到,所以就讲一下。
什么是反射

Java提供了一套反射API,该API由Class类与java.lang.reflect类库组成。
该类库包含了Field、Method、Constructor等类。
对成员变量,成员方法和构造方法的信息进行的编程操作可以理解为反射机制。
从官方定义中就能找到其存在的代价,在运行时获得步伐或步伐集中每一个类型的成员和成员的信息,从而动态的创建、修改、调用、获取其属性,而不需要事先知道运行的对象是谁。划重点:在运行时而不是编译时。(不改变原有代码逻辑,自行运行的时间动态创建和编译即可)
https://i-blog.csdnimg.cn/direct/c1aa438acb8b4b66afc5d7d096044608.png
参考毗连:文章 - JAVA安全底子(二)-- 反射机制 - 先知社区
项目创建

创建一个Java项目,名字叫ReflectDemo。
https://i-blog.csdnimg.cn/direct/74a73f4a473e42db9b57ce747635cc2e.png
我这里选择JAVAEE8。
https://i-blog.csdnimg.cn/direct/d7284d8ba14a4456af574466042839b1.png
把这几个东西删除掉,因为没啥用。
https://i-blog.csdnimg.cn/direct/5a9d2f6f211a4cb6add1598f2b8a5c41.png
新建一个类叫User。
https://i-blog.csdnimg.cn/direct/fd5c3f7cf0674be0b54aeccaf60f7601.png
先写入以下代码,这里阐明一下以下四个为成员变量,分别对应3个差别的属性,公共属性、私有属性、保护属性。
public String name = "wlw";

public int age = 20;

private String gender = "man";

protected String job = "sec"; 再写入成员方法,一个为public属性,一个为protected属性。
public void userinfo(String name, int age, String gender, String job) {
      this.name = name;
      this.age = age;
      this.gender = gender;
      this.job = job;

    }
protected void users(String name, String gender) {
      this.name = name;
      this.gender = gender;
      System.out.println("user的成员方法"+name);
      System.out.println("user的成员方法"+gender);
    } 末了再写入两个构造方法,可以看到方法名字都是User,和我们类的名字一样。
public User(){

    }

public User(String name){
      System.out.println("my name"+name);
    }

private User(String name,int age){
      System.out.println(name);
      System.out.println(age);
    } Class对象类获取

OK我们的User类已经写好了,如今来获取它里面的方法。可能有人有疑惑我都知道这个类名叫User为啥还要获取它呢,是这样的,你要对一个类进行操作大概调用,首先必须要获取这个类的类名才行进行下一步,并不是说我们人知道类名就行了,还得让代码知道。
根据全限定类名获取

我们直接Class.forName(“全路径类名”),运行起来成功获取类名。
public static void main(String[] args) throws ClassNotFoundException {
      Class aClass = Class.forName("com.sf.maven.reflectdemo.User");
      System.out.println(aClass);
    } https://i-blog.csdnimg.cn/direct/0731272e046247978b0c02ff101bddfa.png
根据类名获取

第二种是直接类名.class即可获取到类名
Class bClass = User.class;
System.out.println(bClass); https://i-blog.csdnimg.cn/direct/566d6e839e6c4e29a21a2ef87cdee37b.png
根据对象获取

第三种是对象.getClass()直接获取类名。
User user = new User();
Class aClass1 = user.getClass();
System.out.println(aClass1); https://i-blog.csdnimg.cn/direct/5cfb453ea3ad4ee4a114a60f73c0f70d.png
通过类加载器获取

第四种就是我们可以通过类加载器 ClassLoader.getSystemClassLoader().loadClass(“全路径类名”); 来获取类名。
ClassLoader clsload=ClassLoader.getSystemClassLoader();
Class aClass2 = clsload.loadClass("com.sf.maven.reflectdemo.User");
System.out.println(aClass2); https://i-blog.csdnimg.cn/direct/4daf811236b442db94acda5fe1a388e4.png
使用反射获取成员变量

前面我们已经获取到类名了,如今我们使用反射来获取类里面的成员变量。
新建一个类叫GetField,在里面写入我们获取成员变量的方法,先在开头加入我们获取类名的代码才行。
https://i-blog.csdnimg.cn/direct/9dd9f9011f1346d6bfea322e2375959a.png
常见的获取成员变量的方法有以下几种。
https://i-blog.csdnimg.cn/direct/bb0bd2dcd8a642858e7a332f7201b400.png
获取所有公共成员变量

可以看到输出的是我们在User定义的两个public成员变量,name和age。
Field[] fields = aClass.getFields();
for (Field field : fields) {
            System.out.println(field);
      } https://i-blog.csdnimg.cn/direct/ffff6d2ed93f44a8bfcb11f7b3587466.png
获取所有成员变量

可以看到三个差别属性的成员变量均获取到。
Field[] fields1 = aClass.getDeclaredFields();
for (Field field : fields1) {
            System.out.println(field);
      } https://i-blog.csdnimg.cn/direct/e1595fff75f44d3abc7c6eccaca86be3.png
获取单个公共成员变量

可以看到只获取了一个name公共成员变量。
//获取单个公共成员变量
      Field field2 = aClass.getField("name");
      System.out.println(field2); https://i-blog.csdnimg.cn/direct/6be01f32ada34edc8cf557c5a78e77a6.png
获取恣意单个成员变量

可以看到无论是什么属性的成员变量都可以获取到。
//获取单个成员变量
      Field field3 = aClass.getDeclaredField("gender");
      System.out.println(field3); https://i-blog.csdnimg.cn/direct/79118f2943d6487d900298de2d50d9ae.png
成员变量的值修改和获取

看完获取成员变量了,我们再看一下对成员变量的值进行修改还有获取。
首先创建一个对象也就是获取类名,然后获取age这个公共的成员变量,接着获取user对象的age值,末了输出。
可能这里大家有点不明白,前面开头我们不是已经获取类名了,为啥这里还要获取啊。是这样的,前面获取的类名我们只是为了获取其里面的age成员变量,此时我们的age成员变量已经赋值给field4了,field4.get(user)就是获取user类里面的age值,换句话说反面的是我们要从这个User类里面得到这个age的值,也就是说我们new一个别的的类,只要这个类里面有age的成员变量,也会被获取!!!(讲的有点乱,不对还请指正)
//获取成员变量的值
      User user = new User();
      Field field4 = aClass.getField("age");
      Object a = field4.get(user);
      System.out.println(a); https://i-blog.csdnimg.cn/direct/40af049bd11d443394fdb93fca8e17cf.png
接着对User类里面age的值进行修改,通过 field4.set(user,30); 修改user类里面的age值,可以看到输出为30,但是我们并没有去修改user类age成员变量的代码,这就是JAVA反射机制!!!
field4.set(user,30);
Object b = field4.get(user);
System.out.println(b); https://i-blog.csdnimg.cn/direct/27647a0820e84c4e9e9b548a531868d1.png
使用反射获取构造方法

获取类中的构造方法也是有四种方式。
https://i-blog.csdnimg.cn/direct/e76f456642c7441a9be7134cd2365171.png
新建一个类叫GetConstructor,用来专门写获取构造方法的代码,同样记得再开头加上获取类名的代码才行。
获取所有公共构造方法

可以看到获取到了我们前面写好的User(String name) 和 USer() 这两个公共的成员方法。
//获取公共构造方法
      Constructor[] constructors1 = class1.getConstructors();
      for (Constructor constructor : constructors1) {
            System.out.println(constructor);
      } https://i-blog.csdnimg.cn/direct/6503dcf70cba488d94c72527ff79a8f0.png
获取所有构造方法

可以看到无论是public属性还是private属性的构造方法都被获取到了。
//获取所有构造方法
      Constructor[] constructors2 = class1.getDeclaredConstructors();
      for (Constructor constructor : constructors2) {
            System.out.println(constructor);
      } https://i-blog.csdnimg.cn/direct/cdfca4fa3ccb418babab04a5abe7172d.png
获取单个公共构造方法

这个和获取所有的公共构造方法的代码差不多,只不外是指定了 String 类型的构造方法,前面我们写好的String类型的公共构造方法就只有 User(String name) 所以就返回了这个。
//获取单个公共构造方法
      Constructor constructor3 = class1.getConstructor(String.class);
      System.out.println(constructor3); https://i-blog.csdnimg.cn/direct/bfcbd8e6d99343d083d5c44c690dcdc5.png
获取单个私有构造方法

和上一个的差不多,只不外是多了个Declared而已。
//获取单个私有构造方法
Constructor constructor4 = class1.getDeclaredConstructor(String.class, int.class);
System.out.println(constructor4); https://i-blog.csdnimg.cn/direct/03c4945dff00492f8cc1fb83890024c1.png
对构造方法进行操作

setAccessible(true) 临时开启对私有的访问,newInstance 使用构造方法创建对象,通报参数,答应在运行时通过 Constructor 对象调用类的构造方法。
代码逻辑和上面的成员变量修改差不多,上面是从User类里面找age这个成员变量,这里是从User类里面找到符合的构造方法。可以看到我们的两个参数成功传入到 User(String name,int age) 这个构造方法里面,而且成功调用这个构造方法。
//对构造方法进行操作
Constructor constructor5 = class1.getDeclaredConstructor(String.class, int.class);
//临时开启对私有构造方法的访问
constructor5.setAccessible(true);
User uu = (User) constructor5.newInstance("wlwnb666",30);
System.out.println(uu); https://i-blog.csdnimg.cn/direct/993aec9c06d64a3a847444c7f983362a.png

使用反射获取成员方法

获取成员方法的方式也是四种,新建一个类叫GetMethod。
https://i-blog.csdnimg.cn/direct/b5d34121b1f3497fb7eed2ec28fd8fcf.png
获取包罗继续的所有公共成员方法,可以看到输出有很多公共成员方法,这是由于它连JAVA中自带的公共成员方法也一并输出了,并不单单输出我们本身写的。
//获取包括继承的所有公共成员方法
      Method[] methods1 = class1.getMethods();
      for (Method method : methods1) {
            System.out.println(method);
      } https://i-blog.csdnimg.cn/direct/ee08ba7df74d47ff9c17fe1a8a861d78.png
获取不包罗继续的所有成员方法,这里输出了所有我们本身写的成员方法,并没有输出JAVA内置的。
//获取不包括继承的所有成员方法
      Method[] methods2 = class1.getDeclaredMethods();
      for (Method method : methods2) {
            System.out.println(method);
      } https://i-blog.csdnimg.cn/direct/826c7b378e2e4338bb3d22e37144b06f.png
获取单个成员方法,这个要指定我们获取的成员方法名称为 name ,参数类型也要对应上才行。
//获取单个成员方法
Method method3= class1.getDeclaredMethod("users", String.class, String.class);
System.out.println(method3); https://i-blog.csdnimg.cn/direct/06b0eb76336b4c95adfdd9a8be8e7337.png
和上面几乎一样。
//获取单个公共成员方法
Method method4= class1.getMethod("userinfo", String.class, int.class, String.class, String.class);
System.out.println(method4); https://i-blog.csdnimg.cn/direct/911970c449c346b58b50fd659262cd6c.png
对成员方法进行调用,对我们前面写好的 user 成语方法进行调用。
//调用成员方法
User user = new User();
Method method5= class1.getDeclaredMethod("users", String.class, String.class);
method5.invoke(user,"wlwnb666","sex"); 反序列化链条构造

OK,反射的知识点根本都讲完了,那如今我们来构造以下使用链。
先简朴写一个调用计算机的命令实行,这个是调用JAVA中自带的包,我们称之为原生调用。
https://i-blog.csdnimg.cn/direct/0a1a3e0e2a3d4521a53f7e71b3c79e21.png
但是我们想一想,如果是第三方的包,是不是就得要用反射机制来得到命令实行,由于这里我没有引入第三方的包,所以我们就用JAVA自带的包来做一下通过反射实现命令实行的演示,看成是外部的包即可。
首先我们可以看到这个Runtime.getRuntime().exec() 这个命令实行方法是来自 java,lang.Runtime这个类的。
https://i-blog.csdnimg.cn/direct/7a90df2df23249af8c368383978f7b32.png
那么我们就先获取类名和所有的公共成员方法,记得这里要把路径写全,不能只写Runtime。
https://i-blog.csdnimg.cn/direct/72e694188e4d420ea65eb14ca5a3814d.png
可以看到有很多,找到getRuntime这个方法。
https://i-blog.csdnimg.cn/direct/9aa169a39124406aaabb755caddc575b.png
查询一下。
https://i-blog.csdnimg.cn/direct/abc86ab97dd84f21a1ee31b5442368f5.png
如今我们单独把这个getRuntime 获取出来。
https://i-blog.csdnimg.cn/direct/1f017f4cb0bf4d1cbb9892b97744f634.png
别的我们还需获取exec方法,由于exec 需要传参String类型,所以要加String.class。
Method exec = class1.getMethod("exec", String.class); https://i-blog.csdnimg.cn/direct/a7a0fa2e00454f6dbe8300778cea1793.png
整个链条如下,由于exec方法属于实例方法,所以所以 exec. invoke 的第一个参数是 Runtime 实例。
https://i-blog.csdnimg.cn/direct/e433db57ad7c400baec2b6343c62df78.png这里可能有人不理解第三第四行代码,一开始我也不是很懂,后来查了一下大致理解了。首先我们要知道什么是实例对象,实例指的是通过某个类(Class)创建出来的具体对象,例如:Use user = new Use() 这样就创建了一个实例。那么回到我们的代码,可以看到 getRuntime 方法返回了 currentRuntime,而currentRuntime 正是开头创建的实例,也就是说getRuntime 方法返回的是一个实例。
https://i-blog.csdnimg.cn/direct/76ac85c9602343b9a93d882b866e2712.png
那么回到我们构造的链条,Object runtime = method4.invoke(class1); 调用 getRuntime 方法,而且返回一个实例赋值给 runtime ,所以 exec.invoke(runtime, "calc.exe"); 第一个参数是runtime,第二个参数才是命令。
https://i-blog.csdnimg.cn/direct/783b30756a764f549a87686f1e823a40.png
除了上面的说到的链条构造样子,还可以这样子去构造,不外感觉没有第一种简朴明了。
// 使用 Class.forName 获取 Runtime 类
Class c1 = Class.forName("java.lang.Runtime");

// 获取 Runtime 类的默认构造方法
Constructor m = c1.getDeclaredConstructor();

// 设置构造方法为可访问
m.setAccessible(true);

// 使用反射调用 Runtime 类的 exec 方法,执行系统命令 "calc"
c1.getMethod("exec", String.class).invoke(m.newInstance(), "calc");
不安全的反射对象

指应用步伐使用具有反射功能的外部输入来选择要使用的类或代码,
可能被攻击者使用而输入或选择不正确的类。绕过身份验证或访问控制查抄
参考毗连:悟空云讲堂 | 第七期:不安全的反射弊端 - 知乎
文章 - JAVA反序列化 - Commons-Collections组件 - 先知社区

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: JAVA安全—反射机制&攻击链&类对象&成员变量方法&构造方法