初探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/

乐观的悲观主义者。
原文地址:https://www.cnblogs.com/v1ntlyn/p/13549914.html