java安全学习笔记1

基础知识

反射:在运行时根据传入的字符串来决定执行哪个类的哪个方法

在Java安全中,这四个方法尤为重要

  1. Class.forName():获取一个类(作为入口)
  2. newInstance():实例化这个类,相当于new ClassName(),因为普通方法必须是由对象才能使用
  3. getMethod():获取类中的某个函数(公共的)
  4. invoke():执行这个函数

获取Class对象的三种方式:

  1. obj.getclass():需要手里有了一个对象实例,如"abc".getClass()
  2. Test.class: 代码编译时就需要引入这个类(静态的)
  3. Class.forName("类名"):只需知道类的名字,就能获取类

关于Class.forName的一个误区

1. forName 的重载
Class.forName("String") 实际上是调用了:
Class.forName("String", true, currentLoader)

这里的 true 代表参数 initialize(是否初始化)。

2. 初始化的真正含义
很多人以为“初始化”等于“执行构造函数”,这是错的
文档通过 TrainPrint 类的实验证明了执行顺序:

  1. static {}** (静态代码块):在类初始化**时执行(即 Class.forName 时)。
  2. {}** (构造代码块):在实例化**对象时执行(即 newInstance 时)。
  3. public TrainPrint()** (构造函数):在实例化**对象时执行,且在 {} 之后。

结论:
当调用 Class.forName("某各类") 时,不需要实例化该对象(不需要 newInstance),该类中的 static {} 静态代码块就会被立即执行!


重载:

Java中同一个方法,但是接收的参数不一样。如吃饭:

  1. 吃饭(无参数):去食堂随便吃点
  2. 吃饭(苹果):我要吃苹果
  3. 吃饭(牛排+刀叉):我要用刀叉吃牛排

而为了方便记忆,你不用去记eatrice,eatapple,eatsteak,只用记eat就行,Java会根据你传的参数自动判断

比如Runtime类的exec方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Runtime {

// 版本 1:只接受一个字符串(最简单的命令)
// 比如:exec("calc.exe")
public Process exec(String command) { ... }

// 版本 2:接受一个字符串数组
// 比如:exec(new String[]{"echo", "hello"})
public Process exec(String[] cmdarray) { ... }

// 版本 3:接受命令,还接受环境变量
public Process exec(String command, String[] envp) { ... }

// ... 还有好几个版本 ...
}

为什么要在反射中提重载,因为在通过getMethod拿方法时,需要精确指定参数类型,来告诉Java你要的哪个版本。

1
2
3
4
// 错误写法
Class.forName("java.lang.Runtime").getMethod("exec");
//正确写法
getMethod("exec", String.class)

内部类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 这个就是 C1 (外部类/袋鼠妈妈)
public class C1 {

public void run() {
System.out.println("我是袋鼠妈妈,我在跑");
}

// 这个就是 C2 (内部类/小袋鼠)
// 注意:它是写在 C1 的大括号里面的!
public class C2 {
public void sleep() {
System.out.println("我是小袋鼠,我在睡觉");
}
}
}

在编译成.class文件之后,Java会生成C1.class和C1$C2.class这2个文件,使用$作为一个连接符。

在反射调用(Class.forName)时,我们不能直接搜C2,也不能搜C1.C2,我们要使用编译后的真实名字Class.forName("com.example.C1$C2")

反射

getMethod

获得类以后,我们可以继续使用反射来获取这个类中的属性、方法,也可以实例化这个类,并调用方法。`class.newInstance()`的作用就是调用这个类的无参构造函数,这个比较好理解。不过,我们有时候 在写漏洞利用方法的时候,会发现使用 newInstance 总是不成功,这时候原因可能是:

1. 你使用的类没有无参构造函数

2. 你使用的类构造函数是私有的

使用getMethod来通过Runtime类的exec执行命令

Runtime是一个单例模式,他的构造函数是私有的,这意味着我们不能直接用clazz.newInstance(),因为构造函数不让用,只能通过静态方法getRuntime(),让它把造好的唯一对象给你

1
2
3
4
5
6
7
8
9
10
11
public class Runtime {
private static Runtime currentRuntime = new Runtime();

// 私有构造函数,不让你随便 new
private Runtime() {}

// 开放了一个静态方法,让你获取那个唯一的对象
public static Runtime getRuntime() {
return currentRuntime;
}
}

再说到exec方法,exec方法是public,不是static,这意味着我们不能用Runtime来执行,要用一个Runtime的对象

1
2
3
4
//错误
Runtime.exec("whoami")
//正确
new Runtime().exec("whoami")

那我们的思路就明白了Class.forName拿到clazz ->getMethod拿到getRuntime方法->invoke执行getRuntime得到一个对象实例->getMethod拿到exec方法->invoke对对象实例执行exec方法

1
2
3
4
5
Class clazz=Class.forName("java.lang.Runtime")
Method getruntime=clazz.getMethod("getRuntime")
Object rt=getruntime.invoke(clazz)
Method execmethod=clazz.getMethod("exec",String.class)
execmethod.invoke(rt,"whoami")

写成一行就是

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

getContructor

一个类没有无参构造方法,也没有类似单例模式里的静态方法,我们怎样通过反射实例化该类?

可以使用getConstructor。与getMethod类似,getConstructor 接收的参数是构造函数列表类型,因为构造函数也支持重载, 所以必须用参数列表类型才能唯一确定一个构造函数。 获取到构造函数后,我们使用 newInstance 来执行。

比如另外一种执行命令的方式<font style="color:rgb(51,51,51);">ProcessBuilder</font>,通过反射获取其构造函数,然后调用start来执行命令

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

但是在漏洞环境里面,通常不支持这种写法,或者没引入ProcessBuilder这个类,没法强转,我们需要这样写

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

分开写就是这样

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
import java.util.Arrays;
import java.util.List;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class Test {
public static void main(String[] args) throws Exception {
// === 第一步:获取类 ===
Class clazz = Class.forName("java.lang.ProcessBuilder");

// === 第二步:获取构造函数 (解决没有无参构造的问题) ===
// 对应文本:getConstructor 接收的参数是构造函数列表类型
Constructor constructor = clazz.getConstructor(List.class);

// === 第三步:实例化对象 ===
// 对应文本:获取到构造函数后,我们使用 newInstance 来执行
Object pb = constructor.newInstance(Arrays.asList("calc.exe"));

// === 第四步:执行 start 方法 (解决不能强制类型转换的问题) ===
// 对应文本:通过 getMethod("start") 获取到 start 方法
Method startMethod = clazz.getMethod("start");

// 对应文本:invoke 的第一个参数就是 ProcessBuilder Object 了
startMethod.invoke(pb);
}
}

ProcessBuilder有两个构造函数:

  • <font style="color:rgb(51,51,51);">public ProcessBuilder(List<String> command) </font>
  • <font style="color:rgb(51,51,51);">public ProcessBuilder(String... command)</font>

如果我们要用第二个可变长参数(...表示可变长参数),Java在编译时,会把可变长参数编译成一个数组,所以String[] namesString...names是等价的。所以在反射中,将String[].class传给getConstructor即可获取第二种构造函数。由于newInstance本身接受的参数也是一个数组,二者一叠加,就变成了一个二维数组。举例来说:

  • ProcessBuilder 说:“我要一箱苹果(数组)。”
  • newInstance 说:“所有要传给别人的东西,都必须装在箱子里给我,我会把箱子拆了再给别人。”

如果你直接给 newInstance “一箱苹果”
newInstance 拆开箱子,拿出一个“苹果”给 ProcessBuilder。
ProcessBuilder 怒了:“我要的是一箱,你给我一个?”

所以你必须给 newInstance “一个装了一箱苹果的箱子”(二维数组):
newInstance 拆开外面的大箱子,拿出里面的“一箱苹果”给 ProcessBuilder。
ProcessBuilder 开心了。

payload如下

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.getMethod("start").invoke(clazz.getConstructor(
String[].class).newInstance(new String[][]{{"calc.exe"}}))

getDeclared

为了执行一个私有的方法或者构造方法,我们需要用到getDeclared系列的方法。比起getMethod来说:
  • getMethod系列方法获取的是当前类中所有公共方法,包括从父类继承的方法
  • getDeclared系列方法获取当前类中声明的方法,写在这个类里的,包括私有的方法,但是不包含父类里继承的

因为私有方法或者私有构造函数受到保护,我们需要通过setAccessible(True)来把这个锁给砸开。

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());

java安全学习笔记1
https://rightevil.github.io/java安全学习笔记1/
作者
rightevil
发布于
2025年12月14日
许可协议