Java反射深入再理解

admin 2024年10月13日15:37:48评论5 views字数 5223阅读17分24秒阅读模式

星期五实验室

阅读须知

星期五实验室的技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息造成的直接或间接后果和损失,均由使用者本人负责。

星期五实验室拥有对此文章的修改、删除和解释权限,如转载或传播此文章,需保证文章的完整性,未经授权,不得用于其他。

1. 前言

Java反射是一种强大的机制,它允许程序在运行时动态地加载、检查和使用类,以及动态地调用类的方法、访问和修改类的属性。

之前已经简单的学过反射的基本使用,但随着反序列化的深入学习,反射被用到的地方也越来越多,而很多东西都是在反射的基础上做伸展,比如动态代理、Spring的依赖注入等,因此这篇文章打算深入Java反射的了解和使用。

2. 反射多种方式调用exec

在观看前需要了解最基本的Java反射知识,获取methodfield等。首先来认识下我们常见的命令执行的方法Runtime.getRuntime().exec()

Java反射深入再理解
image.png

可以看到Runtime类中只存在一个无参的构造方法,且关键是为private属性,也就是说我们不能直接通过new来创建一个类对象。

一般情况下,一个类的构造方法为public属性,这时就可以通过new Object()实例化一个对象,也就是通过类加载获取类,最终调用他的构造方法的过程。若构造方法为private属性时是没法访问到的,因此不能通过new Runtime()实例化对象,但是还好他提供了一个静态方法getRuntime,从内部创建对象实例,如此便可利用currentRuntime 调用exec方法执行命令。

所以第一种最简单的调用方式:

//第一种方式:
Runtime.getRuntime().exec("calc");

第二种方式:基础反射调用类下的方法即可:

//第二种方式:
Runtime runtime = Runtime.getRuntime();
Class<? extends Runtime> aClass = runtime.getClass();
Method exec = aClass.getMethod("exec", String.class);
exec.invoke(runtime,"calc");

第三种方式:跳过从本身内部实例化对象,直接获取Runtime类,使用反射调用Constructor构造方法,最后再newInstance创建Runtime的对象实例。实例化一个对象对于后面反射调用方法是必要的,因为在最后invoke时需要传入两个参数,一个为实例化的对象,另一个是被调用方法传入的参数,往往第一个会成为最容易犯错和忽视的地方。

当创建了Runtime实例就不用调用getRuntime()方法了,直接反射调用exec方法执行命令即可。

//第三种方式
Class<Runtime> runtimeClass = Runtime.class;
Constructor<Runtime> runtime = runtimeClass.getDeclaredConstructor();
runtime.setAccessible(true);
Runtime runtime1 = runtime.newInstance();
Class<? extends Runtime> aClass = runtime1.getClass();
Method exec = aClass.getMethod("exec", String.class);
exec.invoke(runtime1,"calc");

第四种方式实现与上一种大体一致,本身通过Class.forName().class都能实现反射。同样是上诉的方法创建Runtime实例,后面稍微绕了一下,先调用getRuntime方法,在利用该方法创建的Runtime实例反射调用exec执行命令。其实是大可不必的,这里也是为了加深反射调用的学习才多此一举。

//第四种方式
Class<?> aClass1 = Class.forName("java.lang.Runtime");
Constructor<?> declaredConstructor = aClass1.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Runtime runtime1 = (Runtime) declaredConstructor.newInstance();
Class<? extends Runtime> aClass2 = runtime1.getClass();
Method getRuntime = aClass2.getMethod("getRuntime");
Object invoke = getRuntime.invoke(runtime1);
Class<?> aClass = invoke.getClass();
Method method = aClass.getMethod("exec", String.class);
method.invoke(invoke,"calc");
Java反射深入再理解
image.png

3. 反射调用私有内部类的私有方法

再来看这样一个demo

一个People类,私有有参构造方法,存在一个私有内部类StudentStudent中存在私有无参有参构造和私有方法badBoy,如果说需要反射调用badBoy方法,那么阁下又该如何应对呢?

public class People {
    private String name ;
    private int age ;
    private People(String name, int age) {
        this.name = name;
        this.age = age;
    }
    private void sayHello(){
        System.out.println("Hello World!");
    }
    private void sayGoodBye() {
        System.out.println("GoodBye World~");
    }
    private class Student {
        public String username;
        public int uid;
        private Student() {
        }
        private Student(String username, int uid) {
            this.username = username;
            this.uid = uid;
        }
        private void goodBoy(String username) {
            System.out.println(username + " is a good Boy~");
        }
        private void badBoy(String username) {
            System.out.println(username + " is a bad Boy~");
        }
    }
}

先给出我的代码实现:

public class Main {
    public static void main(String[] args) throws Exception{
        Class<?> people_class = Class.forName("org.example.test.People");
        Constructor<?> constructor = people_class.getDeclaredConstructor(String.class,int.class);
        constructor.setAccessible(true);
        People people = (People) constructor.newInstance("lemono",100);
        Class<?> aClass = Class.forName("org.example.test.People$Student");
        Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
        //由于内部类隐含的持有一个外部类的引用,所以在使用反射获取内部类构造方法的时候。需要在参数中加入外部类的Class对象。
        declaredConstructors[0].setAccessible(true);
        Object o = declaredConstructors[0].newInstance(people);//private org.example.Person$Student(org.example.Person)默认存在一个参数
        //不用数组形式实例化
//        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(people_class);//需带上参数
//        declaredConstructor.setAccessible(true);
//        Object o = declaredConstructor.newInstance(people);
        Method declaredMethod = o.getClass().getDeclaredMethod("badBoy",String.class);
        declaredMethod.setAccessible(true);
        declaredMethod.invoke(o,"lemono");
    }
}
Java反射深入再理解
image.png

首先需要实例化一个People对象,这是必要的,由于是私有构造方法 ,所以利用和上面是一样的了,只是有参的话需要在后面跟上对应的参数类型class

随后是实例化内部类Student,对于内部类还是有一点区别,从类的获取上可以看到是使用的Class.forName("org.example.test.People$Student"),在People后加$访问Student,这是Java特有的形式,随后是反射调用构造方法实例化。

而实际上内部类的构造方法是不同的,通过打印getDeclaredConstructors()(获取类下所有构造方法包括私有,为一个数组) 可以看到,不论是无参构造还是有参,都会包含他的外部类作为第一个参数,即org.example.text.People,所以对于反射方式实例化内部类的话,无参并不是真正的无参了。

Java反射深入再理解
image.png

我这里使用数组访问可避免这个问题,若是使用getDeclaredConstructor()指定获取的话就需要加上外部类作为参数,像这样:

`Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(people_class);`

后面就是常规的反射获取私有方法badBoy了。

4. 总结

反射是Java中很强大的一个功能,即动态修改运行过程中对象的状态,甚至包括私有属性,也正是因为他的强大和灵活,导致一系列的安全问题。在反序列化过程中一定会被拿来动态修改某些值绕过检测判断,最终行成Gadget,所以能够灵活运用就显得很重要了。

FRIDAY LAB

星期五实验室成立于2017年,汇集众多技术研究人员,在工业互联网安全前瞻技术研究方向上不断进取。星期五实验室由海内外知名高校的学院精英及来自于顶尖企业的行业专家组成,且大部分人员来自国际领先、国内知名的黑客战队——浙大AAA战队。作为木链科技专业的技术研发团队,星期五实验室凭借精湛的专业技术水平,为产品研发提供新思路、为行业技术革新探索新方向。

Java反射深入再理解
Java反射深入再理解
Java反射深入再理解
Java反射深入再理解
Java反射深入再理解

扫二维码|关注我们

星期五实验室
FRIDAY LAB

原文始发于微信公众号(星期五实验室):Java反射深入再理解

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月13日15:37:48
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java反射深入再理解https://cn-sec.com/archives/1996199.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息