Java安全学习

Java安全之反射

反射:将类的各个组成部分封装为其他对象

获取Class对象的方式:

  • Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象
  • 类名.class:通过类名的属性class获取
  • 对象.getClass():getClass()方法在Object类中定义着

同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个

Class对象功能:

​ 获取功能:

​ 1.获取成员变量

​ Field[] getFields()

​ Field getField(String name)

​ Field[] getDeclareFields()

​ Field getDeclareField(String name)

​ 2.获取构造方法

​ Constructor<?>[] getConstructors()

​ Constructor getConstructors(类<?>… parameterTypes)

​ Constructor getDeclareConstructor(类<?>… parameterTypes)

​ Constructor<?>[] getDeclareConstructors()

​ 3.获取成员方法

​ Method[] getMethods()

​ Method getMethod(String name,类<?>… parameterTypes)

​ Method[] getDeclareMethods()

​ Method getDeclareMethod(String name,类<?>… parameterTypes)

​ 4.获取类名

​ String getName()

getMethod

  • 获取类的⽅法: forName

  • 实例化类对象的⽅法: newInstance

  • 获取函数的⽅法: getMethod

  • 执⾏函数的方法: invoke

Class.forName 如果你知道某个类的名字,想获取到这个类,就可以使⽤ forName 来获取

如果直接执行这段代码的话是错误的,因为Runtime 类的构造方法是私有的

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

1641487461549.png

只能通过 Runtime.getRuntime() 来获取到 Runtime 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package Le1a;
import java.lang.reflect.Method;

public class day1 {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("java.lang.Runtime"); //加载java.lang.Runtime类
Method execMethod = clazz.getMethod("exec", String.class);
//用getMethod("exec", String.class) 来获取 Runtime.exec 方法
Method getRuntimeMethod = clazz.getMethod("getRuntime");
//getMethod("getRuntime")来获取 Runtime.getRuntime方法,从而获取到获取到 Runtime的对象exec
Object runtime = getRuntimeMethod.invoke(clazz);//invoke执行方法
execMethod.invoke(runtime, "calc.exe");
}
}

1641487608128.png

getConstructor

getConstructor 接收的参数是构造函数列表类型,因为构造函数也支持重载, 所以必须用参数列表类型才能唯一确定一个构造函数

public ProcessBuilder(Listcommand)

获取到构造函数后,我们使用 newInstance 来执行。 比如,我们常用的另一种执行命令的方式ProcessBuilder,我们使用反射来获取其构造函数,然后调用 start() 来执行命令:

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

public ProcessBuilder(String… command)

这个构造函数用到了可变长参数(varargs),在编译的时候会编译成一个数组,如下两种写法在底层是等价的

1
2
public void hello(String[] names) {} 
public void hello(String...names) {}

String[].class 传给 getConstructor ,获取 ProcessBuilder 的第二种构造函数:

1
Class clazz = Class.forName("java.lang.ProcessBuilder"); clazz.getConstructor(String[].class)

在调用 newInstance 的时候,因为这个函数本身接收的是一个可变长参数,我们传给 ProcessBuilder 的也是一个可变长参数,二者叠加为一个二维数组,所以整个Payload如下:

1
2
3
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new
String[][]{{"calc.exe"}})).start();

getDeclared

如果一个方法或构造方法是私有方法,就要用到getDeclared 系列的反射

1
2
3
4
5
6
7
8
9
10
11
12
13
package Le1a;

import java.lang.reflect.Constructor;

public class day3 {
public static void main(String[] args) throws Exception{
Class calc = Class.forName("java.lang.Runtime");
Constructor m = calc.getDeclaredConstructor();
m.setAccessible(true);
calc.getMethod("exec", String.class).invoke(m.newInstance(), "calc.exe");

}
}