zoukankan      html  css  js  c++  java
  • 初探java安全之反射

    什么是反射

    反射机制在java中可以说是非常强大的,很多优秀的开源框架都是通过反射完成的。在java的运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。下面介绍下基于反射技术的函数方法。

    与反射相关的,其实主要就是几个关键的函数方法。可以先从这一段简单的代码看起

    public void execute(String className, String methodName) throws Exception {
        Class clazz = Class.forName(className);
        clazz.getMethod(methodName).invoke(clazz.newInstance()); }
    

    在这段代码里可以先看到几个与Java反射相关的重要方法。

    forname()

    当无法事先知道将加载什么类的时候,就可以用class的静态方法forname来实现动态加载类,例如例如上面的实例代码。

    forname静态方法也有两个版本,如下:

    public static Class<?> forName(String className)throws ClassNotFoundException
    public static Class<?> forName(String name, boolean initialize,ClassLoader loader)throws ClassNotFoundException
    

    第一个就是仅通过指定类名。

    第二个可以指定类名称、是否初始化及指定类加载器。这里第二个参数中的的初始化指的是类的初始化,例如p神在圈子里给出的一个例子:

    public class TrainPrint {
     {
     System.out.printf("Empty block initial %s
    ", this.getClass());
     }
     static {
     System.out.printf("Static initial %s
    ", TrainPrint.class);
     }
     public TrainPrint() {
     System.out.printf("Initial %s
    ", this.getClass());
     }
    }
    

    运行后就可发现,依次调用的顺序是:先static(),然后是{},最后是构造函数。 而值得注意的是,forname方法加载类时会自动初始化该类对象,也就是说,如果forname的参数可控,那么我们就可以构造对应的恶意类,在恶意类的static()内编写恶意代码,这样当forname()执行时就会执行我们的恶意代码。(关于恶意类要如何带入,这里暂不做深入)

    拓展:forname()不是获取类的唯一途径,这里的“类”指的是java.lang.class对象,还可以通过getClass()来获取,区别就是getClass()是Object实例对象的方法,而forname是class类的静态方法。延伸扩展下,再举个栗子:

    Person p = new Person("wpgsec",666);
    

    该句话都做了什么事情?

    1,因为new用到了Person.class.所以会先找到Person.class文件并加载到内存中。
    2,执行该类中的static代码块,如果有的话,给Person.class类进行初始化。
    3,在堆内存中开辟空间,分配内存地址。
    4,在堆内存中建立对象的特有属性。并进行默认初始化。
    5,对属性进行显示初始化。
    6,对对象进行构造代码块初始化。
    7,对对象进行对应的构造函数初始化。
    8,将内存地址付给栈内存中的p变量。

    这样看也就理解了为什么刚刚的代码是那样的运行顺序,以及对初始化的理解。

    总结:forname方法就是用来加载获取类的。

    newInstance()

    此方法用来创建class对象表示的类的新实例。

    那么newInstance()和new有什么区别呢,首先,newInstance()是一个方法,而new是一个关键字,另外,newInstance()的使用有局限,它生产的实例对象只能调用无参的构造函数,而且如果一个类的类构造函数是私有的话,newinstance()方法也是不能成功实例化的,这个在写漏洞利用方法的时候就会遇到,比如最常见的:

    Class clazz = Class.forName("java.lang.Runtime");
    clazz.getMethod("exec", String.class).invoke(clazz.newInstance(), "id");
    

    运行后会发生这样的错误,

    Exception in thread "main" java.lang.IllegalAccessException: Class cn.v1nt.test.main can not access a member of class java.lang.Runtime with modifiers "private"
    

    也就是因为Runtime方法的构造函数是私有的,导致不能直接用newInstance()来实例化,所以只能通过Runtime.getRuntime()来获取Runtime对象。

    修改后的payload:

    Class clazz = Class.forName("java.lang.Runtime");
    clazz.getMethod("exec",String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz),"calc.exe");
    

    总结:forname()会导致类被初始化,newinstace()才会实例化,而new()操作等于初始化+实例化。

    getMethod()

    Method Class.getMethod(String name, Class<?>... parameterTypes)的作用是获得对象所声明的公开方法,第一个参数是方法的名称,第二个参数是方法参数的对象。

    例如一个person类中有个无形参的Speak方法,有个形参为String类型的run方法,则获取Speak方法为:

    person.getMethod("Speak",null);//Speak方法没有形参,所以parameterTypes为null
    

    获取run方法为:

    person.getMethod("run",String.class)//如果对象内的方法的形参是int类型的,则parameterTypes是int.class
    
    

    而在同个类中也有同方法名不同参数的情况,也就是类的重载,所以只有同时指定方法名和参数类型才能唯一确定一个方法,例如一个函数 int Test(int a, String str);对应的getMethod方法为getMethod("Test",int.class,String.class);

    invoke()

    invoke()是实现java反射的重要方法,通过动态的传入参数,来实现动态的调用,就比如上面的栗子:

    clazz.getMethod("exec",String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz),"calc.exe");
    
    

    简单来说就是 method.invoke(class,参数),就可以实现调用class.method(参数),如果method这个方法是普通方法,那么第一个参数就是类对象,如果是静态方法,那第一个参数就是类。另外这里有点注意的就是,如果底层方法是静态的,就比如这里的getRuntime方法,那么可以忽略指定的参数,默认为null。

    总结:getMethod()可以获取类的某个声明方法,而invoke()则可以去调用执行这个获取到的方法。

    getConstructor()

    如果一个类中没有无参构造方法,也没有静态的获取对象方法,换句话说,就是上面的提到的newinstance()和getMethod().invoke()都用不上了,那怎么继续通过反射来实例化指定的类呢?

    getConstructor(Class<?>... parameterTypes)的作用是根据参数类型(可变参数)来获取公共的构造器Constructor[](public),通过这个方法获取到构造函数后,使用newInstance来执行就可以解决刚才的问题。

    Class clazz = Class.forName("java.lang.ProcessBuilder");
    clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));
    
    

    其实也就是相当于:

    Class clazz = Class.forName("java.lang.ProcessBuilder");
    ((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();
    
    

    getDeclaredMethod()

    public Method getDeclaredMethod(String name, Class... parameterTypes)
    
    

    如果类的构造方法是私有的,比如上面的提到的Runtime类,除了利用Runtime类本身的静态方法getRuntime()来生成对象,还能用getDeclaredMethod方法。

    getDeclaredMethod方法获取的是类自身声明的所有方法,包含public、protected和private方法。跟getMethod的区别是,getMethod方法获取的是类的所有共有方法,包括自身的所有public方法,和从基类继承的、从接口实现的所有public方法。

    例如下面的例子,getDeclaredConstructor也是getDelareMethod系列方法,

    Class clazz = Class.forName("java.lang.Runtime");
    Constructor m = clazz.getDeclaredConstructor();
    m.setAccessible(true);//改变作用域
    clazz.getMethod("exec", String.class).invoke(m.newInstance(), "calc.exe");
    
    

    总结

    可能这些函数方法和知识点都比较基础,对java安全的学习系列也是刚刚开始,后面的文章会再慢慢深入扩展和补充。

    Reference:

    https://blog.csdn.net/huangliniqng/article/details/88554510

    https://govuln.com/topic/924/

    乐观的悲观主义者。
  • 相关阅读:
    现代操作系统-读者/写者问题
    现代操作系统-进程互斥
    关于网页强制被跳转到wpkg.org的解决
    Leetcode Count Prime
    Leetcode Add Two Numbers
    Leetcode Two Sum
    can't find -lsocket的解决办法
    删除Windows右键不用的选项
    Linux下的另一个词典GoldenDict
    spark执行例子eclipse maven打包jar
  • 原文地址:https://www.cnblogs.com/v1ntlyn/p/13549914.html
Copyright © 2011-2022 走看看