内部类

就是存在于类的内部的类,叫内部类

1
2
3
4
5
6
7
8
9
10
11
12
package org.javasec.FirstReflect;

public class Main {
public static void main(String[] args) throws Exception {
//TestReflect2.test();
Outer o = new Outer();
Class<?> i2=Class.forName("org.javasec.FirstReflect.Outer$inner");
Outer.inner i1 = new Outer.inner();
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.javasec.FirstReflect;

class Outer {
static {
System.out.println("[Outer]: 静态代码块-执行成功");
}

static class inner{
static {
System.out.println("[inner]: 静态代码块-执行成功");
}
}
}

观察Java编译器编译后的.class文件可以发现,Outer类被编译为Outer.class,而Inner类被编译为Outer$Inner.class

Java的普通类 C1 中支持编写内部类 C2 ,而在编译的时候,会生成两个文件: C1.class 和

C1$C2.class ,我们可以把他们看作两个无关的类,通过 Class.forName(“C1$C2”) 即可加载这个内

部类

获取字段

  • Field getField(name):根据字段名获取某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.javasec.FirstReflect;

import java.lang.reflect.Field;

public class TestReflect2 {
static void test() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class<User> c1=User.class;
//拿到public字段
System.out.println(c1.getField("age"));
//拿到private字段
Field f1=c1.getDeclaredField("username");
System.out.println(f1);
User u1=new User();
//设置访问权限,允许访问private
f1.setAccessible(true);
//通过反射获取到的字段,传入实例化对象反射获取字段值
System.out.println(f1.get(u1));
}
}

输出

1
2
public int org.javasec.FirstReflect.User.age
private java.lang.String org.javasec.FirstReflect.User.username

调用方法

  • Method getMethod(name, Class...):获取某个publicMethod(包括父类)
  • Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有publicMethod(包括父类)
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
1
2
3
4
5
6
7
User u1=new User();
f1.setAccessible(true);
System.out.println(f1.get(u1));
//反射获取public方法
Method m1=c1.getMethod("getUsername");
//传入实例化对象invoke调用方法
System.out.println(m1.invoke(u1));

输出

1
2
[User]: getter方法-执行成功
admin

调用构造方法

  • getConstructor(Class...):获取某个publicConstructor
  • getDeclaredConstructor(Class...):获取某个Constructor
  • getConstructors():获取所有publicConstructor
  • getDeclaredConstructors():获取所有Constructor
1
2
3
4
5
6
7
8
Constructor<User> con2=c1.getConstructor();
System.out.println(con2);
//反射获取私有构造方法
Constructor<User> con3=c1.getDeclaredConstructor(String.class,int.class);
con3.setAccessible(true);
System.out.println(con3);
//调用
con3.newInstance("test",20);

输出

1
2
3
4
public org.javasec.FirstReflect.User()
private org.javasec.FirstReflect.User(java.lang.String,int)
[User]: 实例初始化-执行成功
[User]: 有参构造方法-执行成功

私有构造方法

我们知道Class.newInstance()是调用类的无参构造方法。但是有些时候,我们编写poc时,常常newInstance失败,原因无非两种:

  1. 该类没有无参构造方法,需要传入参数
  2. 构造方法是私有的,不能直接newInstance

举个例子,比如我们最常见的java.lang.Runtime

如果我们这样写,先获取exec方法,然后通过newInstance实例化对象调用无参构造方法,传入invoke方法执行exec,看起来流程上没有问题

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
package org.javasec.FirstReflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestRun {
static void test() {
Class<?> clazz = null;
try {
clazz = Class.forName("java.lang.Runtime");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Method exec = null;
try {
exec = clazz.getMethod("exec", String.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
try {
exec.invoke(clazz.newInstance(), "whoami");
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
}

}

}

但是运行却报错了

image-20251105145813407

没有访问权限。

我们跟进看看java.lang.Runtime

image-20251105145928221

发现他的构造方法是私有的,那么就不能这样直接调用newInstance来实例化这个类,因为构造方法私有

那先说点题外话,为什么会有类的构造方法是私有的呢,这样设计的意图是什么?

这其实涉及到一个很常见的设计模式:”单例模式”,有时候工厂模式也会写成类似

比如,对于Web应用来说,数据库连接只需要建立一次,而不是每次用到数据库的时候再新建立一个连

接,此时作为开发者你就可以将数据库连接使用的类的构造函数设置为私有,然后编写一个静态方法来获取实例

1
2
3
4
5
6
7
8
9
public class TrainDB {
private static TrainDB instance = new TrainDB();
public static TrainDB getInstance() {
return instance;
}
private TrainDB() {
// 建立连接的代码...
}
}

换句话说,因为每new一个对象,就会自动调用构造方法,但是在有些场景下,并不希望被多次调用构造方法,想要通过某种方式封装一下,让触发构造方法的这个行为更可控,于是把构造方法设计为私有,这样实例化时不会自动触发构造方法,而是通过一个静态方法来控制,静态方法返回一个静态字段,真正的new操作在静态字段里

于是这种情况下,我们想要实例化该类就需要按照他的设计来

我们知道invoke 的作用是执行方法,它的第一个参数是:

如果这个方法是一个普通方法,那么第一个参数是类对象

如果这个方法是一个静态方法,那么第一个参数是类

于是我们这样写

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
package org.javasec.FirstReflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestRun {
static void test() {
Class<?> clazz = null;
try {
clazz = Class.forName("java.lang.Runtime");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Method exec = null;
try {
exec = clazz.getMethod("exec", String.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
try {

exec.invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc");

} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}

}

}

就能成功执行命令了