keac's Bolg.

Java 反射机制

字数统计: 1.3k阅读时长: 5 min
2022/05/23 Share

前言

Java反射机制可以无视类方法和变量去访问权限修饰符,并且可以调用任何类的任何方法,访问并修改成员变量值。也就是说能控制反射的类名、方法名和参数的前提下,如果我们发现了一个java反射调用的漏洞,我们就可以为所欲为了。

反射 Reflection

反射是java的特性之一,在C/C++语言中不存在反射这个概念,有了反射java可以更加灵活调控自身运行时的信息,操作类和对象的内部属性。

反射的用途非常广,在我们使用IDE工具进行开发的时候,我们输入一个对象并且想调用它的属性或者方法的时候,IDE会自动列出它的属性和方法,这些就是通过反射来实现的。再比如JavaBean和JSP之间的调用也是通过反射来实现的。最重要的就是各种通用用途的开发框架,比如Spring框架以及ORM框架,他们都是通过反射机制来实现的。

对象可以通过反射获取其他的类,类可以通过反射拿到所有方法(包括私有),拿到的方法可以调用,通过反射功能我们可以给Java这种静态语言加上动态特性。

动态特性是什么呢,phith0n 师傅提到过 “一段代码,改变其中的变量,将会导致这段代码产生功能性的变化,我称之为动态特性”

用途

获取对象类

1
2
3
4
5
6
7
Class.forName("java.lang.Runtime")
Runtime.class
runtime.getClass()
ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime") // 和forName类似 但是不会执行static代码

Class.forName("[D") // 相当于double[].class
Class.forName("[[Ljava.lang.String")// 相当于 String[][].class

反射调用内部类的时候需要用$

如 com.xxx.test 内有个H的类,调用的时候就需要写成com.xxx.test$H

forName 有两个函数重载:

1
2
3
Class.forName(className)
// 等于
Class.forName(className,true,currentLoader)

默认情况下forName 第一个参数是类名,第二个参数表示是否初始化,第三个参数就是Classloader

是否初始化是什么意思呢

1
2
3
4
5
6
7
8
9
10
11
public class TrainPrint {
{
System.out.printf("Empty block initial %s\n", this.getClass());
}
static {
System.out.printf("Static initial %s\n", TrainPrint.class);
}
public TrainPrint() {
System.out.printf("Initial %s\n", this.getClass());
}
}

比如这段代码,首先调用的是static{} 其次是 {},最后是构造函数。

其中static{} 就是在类初始化的时候调用的,而{}中的代码会放在构造函数super()的后面,但在当前构造函数内容的前面。

所以说,forName 的初始化就是告诉Java虚拟机是否执行“类初始化”。

比如我们有如下函数,name参数可控

1
2
3
public void ref(String name)throws Exception{
Class.forName(name);
}

我们就可以编写一个恶意类,放在static{}中,从而执行

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Touch {
static {
try {
Runtime runtime=Runtime.getRuntime();
String[] commands={"touch","/tmp/ss"};
Process pc= runtime.exec(commands);
pc.waitFor();

}catch (Exception e){

}
}
}

获取类方法

1
2
3
4
5
6
getDeclaredMethods() //获取所有方法,不包括继承方法
getMethods() //获取所有public方法,包括继承的 Method[]
getMethod("exec", String.class) //获取一个特定的方法,后面的对应参数类型的Class对象 只可以public修饰符
getDeclaredMethod("exec", String.class) //获取所有修饰符的方法,跟getMethod一样
getDeclaredConstructor // 获取类构造方法,可以获取到私有
getConstructor //获取类构造方法

如果构造方法有一个或多个参数的情况下我们应该在获取构造方法时候传入对应的参数类型数组

1
clazz.getDeclaredConstructor(String.class, String.class)

获取到Constructor 后就可以拿来创建实例,有参数需要传入

1
constructor.newInstance("test","test")

反射调用方法

1
getRuntime.invoke(runtime,"whoami") //第二个参数不是必须的

获取成员变量

1
2
3
4
5
6
7
Field fields = xxx.getDeclaredFields //获取所有成员字段,不包括父类的声明字段
getFields //获取所有public字段,包括父类的字段
getDeclaredField //获取单独的成员变量
getField //获取单独的成员变量只限public,包括父类

Object obj =field.get(String.class) //获取变量值
field.set("对象","修改值")

如果修改的值没有权限也可以使用

1
field.setAccessible(true)

如果修改被final关键词修饰(最终方法,无法被子类重写)

1
2
3
4
5
6
7
8
9
10
11
// 反射获取Field类的modifiers
Field modifiers = field.getClass().getDeclaredField("modifiers");

// 设置modifiers修改权限
modifiers.setAccessible(true);

// 修改成员变量的Field对象的modifiers值 ~取反 & 与操作
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);

// 修改成员变量值
field.set(类实例对象, 修改后的值);

调用Runtime

Java任何一个类都有一个或多个构造方法,如果没有会在类编译的时候自己创建一个无参构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 获取Runtime类对象
Class runtimeClass1 = Class.forName("java.lang.Runtime");

// 获取构造方法,构造方法是私有的所以需要通过反射去修改权限
Constructor constructor = runtimeClass1.getDeclaredConstructor();
constructor.setAccessible(true);

// 创建Runtime类示例,等价于 Runtime rt = new Runtime();
Object runtimeInstance = constructor.newInstance();

// 获取Runtime的exec(String cmd)方法
Method runtimeMethod = runtimeClass1.getMethod("exec", String.class);

// 调用exec方法,等价于 rt.exec(cmd);
Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);

// 获取命令执行结果
InputStream in = process.getInputStream();

// 输出命令执行结果
System.out.println(IOUtils.toString(in, "UTF-8"));
CATALOG
  1. 1. 前言
  2. 2. 反射 Reflection
    1. 2.1. 用途
      1. 2.1.1. 获取对象类
      2. 2.1.2. 获取类方法
      3. 2.1.3. 获取成员变量
    2. 2.2. 调用Runtime