拖了很久,才开始学习JAVA 安全相关,最近很是摆烂,希望能够快速进入学习状态,冲冲冲!
同时说明一下,本文大多参考自Y4tacker大师傅的JavaSec
,因此例子大多是引用的,大师傅们勿喷。
什么是JAVA 反射。接下来引用一下Y4tacker大师傅的话
Java反射机制是在运行状态时,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。
简单的说,就是通过java反射机制,我们可以获取任意类的成员方法、变量这些,同时,可以创建JAVA类实例,调用任意类方法。
还有就是想说一下这里为什么称为反射,在JAVA中,我们可以这样理解:通过new一个对象而调用其中方法的,称为”正射”,而不通过new一个对象取得其中方法的,称为”反射”。
反射机制是java实现动态语言的关键,也就是通过反射机制实现类动态加载
1、静态加载:编译时加载相关的类,如果没有就报错,依赖性过强
2、动态加载:运行时加载需要的类,如果运行时不使用该类,即使不存在也不会报错,降低了依赖性。
示例如下
import java.util.*;
import java.lang.reflect.*;
public class Main{
public static void main(String[] args) throws Exception{
Scanner Scanner = new Scanner(System.in);
System.out.println("请输入key");
String key = Scanner.next();
switch (key){
case "1":
Dog dog = new dog();
dog.cry();
break;
case "2":
Class cls = Class.forName("Person");
Object o = cls.newInstance();
Method m = o.getMethod("hi");
m.invoke(o);
break;
}
}
}
此时去运行javac Main.java
会发现无法编译成功,因为dog类已经是静态加载机制,这个需要提前加载,而这里没有,故报错,而如果有Dog类,即使没有Person类,编译也不会报错,会正常生成class
文件,但当运行时会报错。
在JAVA的Lang包中存在一个名为Class
的静态类,在JAVA程序编译加载某一个类时,JAVA.Lang.Class就会实例化出一个对象,这个对象存储了类的所有信息。因此,我们可以通过这个Class
对象拿到这个类的信息。接下来我们先了解一些方法。
forName方法可以获取类中的所有属性包括类名。
示例如下
Class.forName(classname): 获取classname类中的所有属性
Class qwq = Class.forName("java.lang.Runtime");
//获取Runtime类中的所有属性,并赋值给clazz类
书接上文,我们在示例中将Runtime
类中的所有属性赋值给了qwq
类,接下来我们想要调用qwq类中的某个方法的话,需要三步
1、对类进行实例化(实例化对象)
2、获取方法
3、执行方法
接下来分步进行。
第一步,是实例化对象,此时也就引入了我们的**newInstance()**方法,对该方法进行简单解释,此方法可以实例化对象,并触发类的构造方法。
所以此时的话对象就创建完成了,接下来就是获取方法了。
我们在获取完对象后,对对象中的Public方法获取的方式是采用*getMethod()*函数,这个函数的具体参数如下
getMethod("方法名,参数类型(如String.class)")
此时就可以获取到方法了,接下来我们只需要进行执行方法即可,此时也就引入了我们的最后一个函数
involve可以执行方法,如果是一个普通方法,则involve
的第一个参数为该方法所在的对象,如果是静态方法则第一个参数是Null
或者该方法所在的类,第二个参数为要执行方法的参数。
上述只是一些常见的方法,并不是全部的,比如获取类的方法,不止有forName
函数,接下来开始进一步讲解。
JAVA反射操作的是java.lang.Class
对象,上文中提到过Class
对象,在类被编译加载时,它会存储类的相关信息,所以我们这里需要首先获取到类。上述说了一个方法,forName
方法,它并不是唯一的方法,还有两种常用的方法,具体如下
1、getClass(): 当上下文中存在某个类的实例化对象,我们可以通getClass()方法直接获取它的类,示例如下:
String s = "qwq";
Class cla = s.getclass();
2、类名.class: 如果已经加载了一个类,我们知道类中的一个静态变量或是其他,此时便可直接获取,示例如下:
Class cla = String.class
对于forName
方法,获取类的方式如下
3、Class.forName("完整类名"),当我们知道一个类的完整类名时,可以通过静态方法Class.forName()获取
Class cla = Class.forName("java.lang.String")
//获取java.lang.String的全部方法
上文中说了获取类的方法,接下来说一下获取字段的方式,获取字段的话,主要用到以下两个函数
getFields():获得某个类的所有的公共(public)的字段,包括父类中的字段。
getDeclaredFields():获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。
这里借用一下Y4大师傅的示例。
import java.lang.reflect.Field;
public class test {
public static void main(String[] args) throws Exception {
Class stiClass = StuInfo.class;
// 获取public字段"age":
System.out.println(stiClass.getField("age"));
// 获取继承的public字段"name":
System.out.println(stiClass.getField("name"));
// 获取private字段"money":
System.out.println(stiClass.getDeclaredField("money"));
// 获得值,name.get里面参数需要该类对象,而不是.class
Field name = stiClass.getField("name");
System.out.println(name.get(stiClass.newInstance()));
// 设置值
StuInfo stuInfo = new StuInfo();
Field money = stiClass.getDeclaredField("money");
money.setAccessible(true);
money.set(stuInfo,2333333);
System.out.println(stuInfo);
}
}
class StuInfo extends PersonInfo{
public int age;
private int money;
@Override
public String toString() {
return "StuInfo{" +
"name=" + name +
", money=" + money +
'}';
}
}
class PersonInfo{
public String name = "quan9i";
}
这里需要说明的是**money.setAccessible(true);*这个语句,它是取消了money*变量的特权模式,本来money是private
的,不可被更改,访问这些,而当有这个语句后,就变的与public
相同了(小白个人观点,可能有误)。
这里涉及了四个函数,具体如下所示
1、Method getMethod(name, Class...):获取某个public的方法(包括父类)
2、Method getDeclaredMethod(name, Class...):获取当前类的某个方法(不包括父类)
3、Method[] getMethods():获取所有public的方法(包括父类)
4、Method[] getDeclaredMethods():获取当前类的所有方法(不包括父类)
示例如下
String name = "quan9i";
Method substring = String.class.getMethod("substring", int.class);
System.out.println(substring.invoke(name,3));
如果调用的方法是静态方法。那么invoke
方法传入的第一个参数永远为null
// 获取Integer.parseInt(String)方法,参数为String:
Method m = Integer.class.getMethod("parseInt", String.class);
// 调用该静态方法并获取结果:
Integer n = (Integer) m.invoke(null, "23333");
System.out.println(n);
java.lang.Runtime
因为有一个exec
方法可以执行本地命令,所以在很多的payload
中我们都能看到反射调用Runtime
类来执行本地系统命令,通过学习如何反射Runtime
类也能让我们理解反射的一些基础用法。
执行的Payload
如下
Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")),"calc");
这个的话我们其实可以把他进行拆分,这句话拆分为五段,如下所示
Class clazz = Class.forname("java.lang.Runtime");
Method execMethod = clazz.getMethod("exec", String.class);
Method getRuntime = clazz.getMethod("getRuntime");
Object currentRuntime = getRuntime.invoke(clazz);
execMethod.invoke(currentRuntime, "calc.exe");
对其进行简单讲解
1、首先通过反射获取到Runtime类
2、通过反射获取到Runtime类的exec方法
3、通过反射获取到Runtime类的getRuntime方法
4、创建Runtime对象并调用exec方法
5、调用exec方法并执行命令exec "calc.exe"
第一个问题,我们可以用一个新的反射方法getConstructor
。
getConstructor(Class...):获取某个public的Constructor;
getDeclaredConstructor(Class...):获取某个Constructor;
getConstructors():获取所有public的Constructor;
getDeclaredConstructors():获取所有Constructor。
和getMethod类似,getConstructor
接收的参数是构造函数的的列表类型,因为构造函数也支持重载,所以要用参数列表类型才能唯一确定一个构造函数
比如我们常用的另一种执行命令的方式ProcessBuilder,我们使用反射来获取其构造函数,然后 调用start()
来执行命令
接下来简单介绍一下ProcessBuilder。
ProcessBuilder用于创建操作系统进程,它提供一种启动和管理进程(也就是应用程序)的方法,我们可以通过实例化这个类并且通过反射调用其中的start方法来开启一个子进程 。当getRuntime
被禁用时,可以用ProcessBuilder
来执行命令。它有两种构造函数
public ProcessBuilder(List<String> command)
public ProcessBuilder(String... commang)
接下来看这个执行命令的Payload
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();
执行过程如下
1、首先利用反射获取ProcessBuilder类;
2、获取clazz(ProcessBuilder)形参列表为List<String> command的构造函数;
3、将获取到的构造函数利用newInstance进行实例化,调用构造函数;
4、对构造函数传入的参数为 calc.exe,并且用Arrays.asList方法将要执行的命令转为List类型;
5、返回List类型的command;
使用getDeclared
系列方法,函数介绍如下
getConstructor(Class...):获取某个public的Constructor;
getDeclaredConstructor(Class...):获取某个Constructor;
getConstructors():获取所有public的Constructor;
getDeclaredConstructors():获取所有Constructor。
getMethod
系列方法获取的是当前类中所有公共方法,包括从父类继承的方法;getDeclaredMethod
系列方法获取的是当前类中“声明”的方法,是实写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了。举个例子,我们之前提到过Runtime的构造方法是私有的,所以我们要通过Runtime.getRuntime()
来获取对象,其实我们也可以直接用getDeclaredConstructor
来获取这个私有的构造方法实例化对象,进而执行命令:
Class clazz = Class.forName("java.lang.Runtime");
Constructor m =clazz.getDeclaredConstructor();
m.setAccessible(true);
clazz.getMethod("exec",String.class).invoke(m.newInstance(), "calc.exe");
这里我们在获取到私有方法后,要用setAccessible()
方法使这个私有方法可以被访问,其他的就和之前介绍的反射一样了,如果不用setAccessible()
方法修改作用域这个方法是仍然不能调用的