放假前就一直想着要在暑假学一下java的漏洞,大体看了一下发现java的内容不是一般的多,就从java的反射开始学吧。

java的反射机制

Java的反射机制是Java非常重要的动态特性,我们学过的php就有着丰富的动态特性。而java并没有php那么的灵活,所以java反射所带来的动态特性就显得和重要了。通过反射我们不仅可以得到任何类的成员方法,构造方法,成员属性,还可以实例化类,调用任意的类方法、修改任意的类成员变量值等

放射的步骤

放射的步骤分为如下几个

1
2
3
4
1.反射获取类
2.获取类的方法
3.创建类的实例化对象
4.使用类的方法

下面我会给出每个步骤所使用的函数。

反射获取类

forName

在反射中最常用到的获取类的函数就是forname。
其有如下两种调用方法

1
2
Class<?> forName(String name)
Class<?> forName(String name, **boolean** initialize, ClassLoader loader)

Class<?> forName(String name, **boolean** initialize, ClassLoader loader)中第二个参数为是否初始化,第三个参数为加载器的选择。
默认的调用方法即第一种调用方法,其默认为会初始化。
关于初始化我这里稍微展开说一下
首先我们看如下类初始化后输出的结果
1
2
3
4
5
6
7
8
9
10
11
12
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());
}
}


这个类初始化后输出结果为
1
2
3
Static initial class TrainPrint
Empty block initial class TrainPrint
Initial class TrainPrint

这是因为初始化的过程按顺序分为三步
1.静态初始化块
静态初始化块是在类加载完成后、实例化任何对象之前执行的。
静态初始化块只执行一次,用于初始化类的静态变量。
2.实例初始化块
实例初始化块是在每次创建类的实例时执行的。
实例初始化块在构造方法之前执行。
3.构造方法
构造方法是在实例初始化块之后执行的,用于初始化对象的实例变量。

语法

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class clazz=Class.forName("java.lang.Runtime");
System.out.println(clazz);
}
}


--------------------------------------------------------------------------------
class java.lang.Runtime

ClassLoader.getSystemClassLoader().loadClass

使用这个也可以加载类,但是和forName不同的是这个方法不会初始化类

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");
System.out.println(clazz);
}
}


---------------------------------------------------------------------------------------------
class java.lang.Runtime

类名.class

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = java.lang.Runtime.class;
System.out.println(clazz);
}
}


---------------------------------------------------------------------------------------------
class java.lang.Runtime

getclass()

当程序里有以及加载的类时可以使用getclass来获取类

1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");
Class<?> clazz2= clazz.getClass();
System.out.println(clazz2);
}
}

获取方法

getMethod()

这个函数只能获取一个方法,返回的时一个方法

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

public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException{
// 使用 Class.forName 加载类
Class<?> clazz = Class.forName("java.lang.Runtime");

// 获取指定方法
Method method = clazz.getMethod("exec",String.class);

// 打印方法的名称
System.out.println(method);
}
}
----------------------------------------------------------------------------------------------------------------
public java.lang.Process java.lang.Runtime.exec(java.lang.String) throws java.io.IOException

getMethods

返回的是一个方法的数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.lang.reflect.Method;

public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException{
// 使用 Class.forName 加载类
Class<?> clazz = Class.forName("java.lang.Runtime");

// 获取指定方法
Method[] methods = clazz.getMethods();

for(Method method:methods)
System.out.println(methods);
}
}
--------------------------------------------------------------------------------------------------------------------
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd
[Ljava.lang.reflect.Method;@70177ecd

getDeclaredMethod

用法与getMethod和getMethods一样即可以获取单个也可以获取多个

创建对象实例

newInstance()

这个函数是通过调用类的无参构造方法来实例化对象的,即当要实例化的对象没有无参构造方法或者其构造方法为私有的时这个函数将无法将对象直接实例化。
最常见的便是在实例化Runtime类时

1
2
3
4
5
6
7
8
9
10
11
12

import java.lang.reflect.Method;

public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException,IllegalAccessException,InstantiationException {
Class<?> clazz = Class.forName("java.lang.Runtime");
Object obj= clazz.newInstance();
System.out.println(obj);
}
}
------------------------------------------------------------------------------------
Exception in thread "main" java.lang.IllegalAccessException: class Main cannot access a member of class java.lang.Runtime (in module java.base) with modifiers "private"

我们可以看到上面的代码报错了,这是因为Runtime类的构造方法是私有的,所以我们无法直接使用newInstance()得到实例对象,具体怎么得到我会在下文给出

语法

class.newInstance()

使用方法

invoke()

这个函数的使用方法如下

1
public Object invoke(Object obj,Object... args)

一般在反射中与newInstance()联合起来用
1.如果方法是一个普通方法,那么第一个参数是类对象

2.如果方法是一个静态方法,那么第一个参数是类
打个比方我们在正常使用类的方法是安下面的方法来使用的
[1].method([2],[3],....)
而invoke是如下方法使用的
method.invoke([1],[2],[3])
即其是将实例对象放在了第一个参数里

构造Runtime的命令执行

首先上文我有提到,Runtime类的构造方法是私有的,即我们使用如下的代码带反射得到exec是会报错的

1
2
3
4
5
6
7
8
9
import java.lang.reflect.Method;

public class Main {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.newInstance(), "id");
}
}



可是既然开发者给出这个包我们就应该可以使用,而Runtime是单例模式,只能通过Runtime.getRuntime()来得到实例

即我们只要利用invoke来执行Runtime的getRuntime方法就可以得到Runtime的实例对象代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

import java.lang.reflect.Method;

public class Main {
public static void main(String[] args) throws Exception {
Class clazz=Class.forName("java.lang.Runtime");
Object Run=clazz.getMethod("getRuntime").invoke(clazz);
System.out.println(Run);
}

}

---------------------------------------------------------------------------------------------------
java.lang.Runtime@2f4d3709


可以看到成功通过反射得到了Runtime的实例既然得到了Runtime的实例那么就可以使用Runtime里的所有方法了
1
2
3
4
5
6
7
8
9
10

import java.lang.reflect.Method;

public class Main {
public static void main(String[] args) throws Exception {
Class clazz=Class.forName("java.lang.Runtime");//Runtime.getRuntime.exec("calc")
clazz.getMethod("exec",String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz),"calc");
}

}


可以发现成功使用exec弹计算机(说实话个人感觉这个反射的过程是从后往前看,先看方法在看类。感觉有点像递归)

没有无参构造函数和私有函数构造的反射

上文我们学了反射的皮毛,对反射有了一定的了解但是还是有两个疑问就是如果没有无参构造函数要怎么构建反射,如果是私有的函数但是没有Runtime的单例模式我们应该怎么来构造反射。

没有无参构造函数构造的反射

要想构造这个反射我们要先了解一个函数getConstructor()

getConstructor()

这个函数与getmethod相似但是这个函数可以获取选定参数的构造方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.lang.reflect.Constructor;
import java.util.List;

public class Main {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("java.lang.ProcessBuilder");
Constructor constructor = clazz.getConstructor(List.class);
System.out.println(constructor);
}
}

------------------------------------------------------------------------------------------------------
public java.lang.ProcessBuilder(java.util.List)

我们可以看到getConstructor()得到了一个参数为List<String>的构造方法。

newInstance(…)

这里我们要在讲一下newInstance(),这个我在上文有讲过但是讲的不够详细。newInstance()其实有两种实例化的方式,分别为class.newInstance()和Constructor.newIstance(<参数>)第一种就是我上文说的无参构造实例,而第二种就是利用构造方法和其匹配的参数来进行实例化如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Arrays;

public class Main {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("java.lang.ProcessBuilder");
Constructor constructor = clazz.getConstructor(List.class);
Object obj=constructor.newInstance(Arrays.asList("calc"));
System.out.println(obj);
}

}
----------------------------------------------------------------------------------------------------------------
java.lang.ProcessBuilder@1d81eb93

可以发现成功实例化,而newInstance内的参数被传到了实例中。

ProcessBuilder命令执行的反射

我们首先要了解一下ProcessBuilder,ProcessBuilder内有多种的命令执行构造方法,其中参数也不同,但构造反射的方法大致分为两种。为
public ProcessBuilder(List<String> command)
public ProcessBuilder(String... command)

利用public ProcessBuilder(List command)

要构造这给的反射我们同样先看一下正常的命令执行代码是什么样的

我们可以看到我们只要使用上文的newInstance来创建一个实例,并且传参为List即可再调用Start()即可(注意start()是ProcessBuilder这个类的内置函数而我们再使用newInstance来创建一个实例时其类型为Object无法调用这个方法,所以在反射时我们还要进行类型的转换)
代码如下

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

但是其实我们发现上面的代码其实并不是完全使用反射来写的,我们只要利用invoke来使用start即可以进行强制类型的转换
1
2
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc")));

public ProcessBuilder(String… command)

既然我们都知道了List<String>要怎么构造了那么构造这个的难道其实就不大了。
要构造这个首先我们要先了解一下变长参数。
变长参数就是在创建函数参数不确定数量时使用…来代表,其实变长参数所接受的就是一个数组
在底层中下面的两个构造方法的写法其实是一样的

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

即我们才使用这种函数时传的参数其实就是数组,如我们要将一个数组传入hello这个函数那么我们就可以使用如下方法
1
2
String[] haha={"lala","haha"};
hello(haha);

心态崩了,本来都写完了,结果不知道为什么出了bug没保存下来白写了,下面我就随便写了,烦死了。
获取 ProcessBuilder 的第二种构造函数:

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

获得其构造函数就可以得到其实例,即可以进行命令执行
1
2
3
Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new
String[][]{{"calc.exe"}})).start();


其全反射为
1
2
3
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.gerMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new
String[][]{{"calc.exe"}}));

私有类的反射构造

私有类的反射涉及到 getDeclared 系列的反射,与普通的 getMethod 、 getConstructor 区别是:
getMethod 系列方法获取的是当前类中所有公共方法,包括从父类继承的方法
getDeclaredMethod 系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了

如上我们通过getDeclaredConstructor可以得到私有的无参构造方法

1
2
3
4
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 修改它的作用域,否则仍然不能调用。