Java反序列化之Java反射与URLDNS链分析

该博客为参考学习笔记博客,仅为本人记录的笔记,所以欢迎大家去Drunkbaby师傅的博客中进行学习!Drunkbaby’s Blog

参考链接:Java反序列化基础篇-02-Java 反射与 URLDNS 链分析

一、Java反射的概念

1.1 正射与反射

  • 正射

我们在编写代码时,当需要使用到某一个类的时候,都会先了解这个类是做什么的。然后实例化这个类,接着用实例化好的对象进行操作,这就是正射。

1
2
Student student = new Student();
student.doHomework("数学");
  • 反射

反射就是,一开始并不知道我们要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。我们以这一段经典的反射代码为例说明。

在上一篇中所讲的Person.java中的Person类,我们新建一个ReflectionTest.java文件来获取Class

1
2
3
4
public static void main(String[] args) {
Person p = new Person();
Class c = p.getClass();
}

接下来的重点问题在于如何理解Class是什么

1.2 反射的理解

  • 反射的作用:让 Java 具有动态性

Java 的反射机制是指在==运行状态==中,对于任意一个类都能够知道这个类所有的属性和方法; 并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为 Java 语言的反射机制。

以下是Drunkbaby师傅的原文:

Java 本身是一种静态语言,为什么这么说,看这一段代码就知道了。

1
Student student = new Student();

那反过来,什么是动态语言呢?PHP 本身就拥有很多动态特性,我们来看这一段代码。在这一段代码里面,我们输入 eval,php 就执行 eval 命令;输入 echo 就执行 echo 命令;这就是语言的动态特性。

1
eval(<?php eval()?>)

1.3 Java Class对象的理解

我们程序在运行的时候会编译生成一个 .class 文件,而这个 .class 文件中的内容就是相对应的类的所有信息,比如这段程序当中:

1
2
3
4
public static void main(String[] args) throws Exception{  
Person person = new Person();
Class c = person.getClass();
}

其实 **person.class 就是 Class**,Class 也就是描述类的类。

Class 类的对象作用是运行时提供或获得某个对象的类型信息。

所以反射其实就是操作 Class,看清楚了,是大 C

二、Java反射运用

2.1 反射组成相关的类

反射机制相关操作一般位于java.lang.reflect包中。

java反射机制组成需要重点注意以下的类:

  • java.lang.Class:类对象;

  • java.lang.reflect.Constructor:类的构造器对象;

  • java.lang.reflect.Field:类的属性对象;

  • java.lang.reflect.Method:类的方法对象;

2.2 反射的使用方法

一些比较常用的反射函数,配合着在反序列化中实现弹shell等等的操作;

  • 获取类的方法:forName

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

  • 获取函数的方法:getMethod

  • 执行函数的方法:invoke

1. 首先需要实例化对象

对于普通用户我们可以采用以下方法创建实例:

1
Person test = new Person();

而我们在创建 Class 类的实例对象却不能使用上述方法,运行会抛出错误

1
Class test = new Class();

image-20240728191119009

因为 Class 类是 private 私有属性,我们也无法通过创建对象的方式来获取 class 对象,那么我们怎样才能够获取到 class 对象呢?一般我们获取 class 对象就有以下三种方法,我们来逐一看看。

  1. 实例化对象的getClass()方法

如果上下文中存在某个类的示例obj,那么我们可以通过obj.getClass()来获取它的Class;例如:

1
2
Person p = new Person();
Class pClass = p.getClass();
  1. 使用类的.class方法

如果你已经加载了某个类,只是想获取到它的 java.lang.Class 对象,那么就直接拿它的 class 属性即可。这个⽅法其实不属于反射。

1
2
// 该类中必须导入Person类
Class personClass = Person.class;
  1. Class.forName(String className):动态加载类

如果你知道某个类的名字,想获取到这个类,就可以使⽤ forName 来获取,后续要利用的话是需要实例化的。

1
Class psClass = Class.forName("com.mango.serialize.Person");

以上是三种获取Class的方法,我们在写一个简单的示例去演示获取Class;

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
package com.mango.reflection;

import com.mango.serialize.Person;

/**
* @Project unSerialize
* @File com.mango.reflection.reflectionTest.java
* @Author mango
* @Date 2024/7/28 14:33
* @Description
*/

public class reflectionTest {
public static void main(String[] args) throws ClassNotFoundException {
// 方法一:实例化对象的getClass()方法
Person p = new Person();
Class class1 = p.getClass();
System.out.println(class1.getName());

// 方法2:使用类的.class方法
Class class2 = Person.class;
System.out.println(class2.getName());

// 方法3:Class.forName(String className):动态加载类
Class class3 = Class.forName("com.mango.serialize.Person");
System.out.println(class3.getName());
}
}

image-20240728192036688

2. 获取成员变量Field

获取成员变量Field位于 java.lang.reflect.Field 包中

  • Field[] getFields() :获取所有 public 修饰的成员变量

  • Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符

  • Field getField(String name) 获取指定名称的 public 修饰的成员变量

  • Field getDeclaredField(String name) 获取指定的成员变量

3. 获取成员方法Method

1
2
3
4
5
6
7
//第一个参数获取该方法的名字,第二个参数获取标识该方法的参数类型
Method getMethod(String name, Class<?>... parameterTypes) //返回该类所声明的public方法
Method getDeclaredMethod(String name, Class<?>... parameterTypes) //返回该类所声明的所有方法


Method[] getMethods() //获取所有的public方法,包括类自身声明的public方法,父类中的public方法、实现的接口方法
Method[] getDeclaredMethods() // 获取该类中的所有方法

Person.java中添加以下代码:

1
2
3
4
5
6
7
8
public void study(String s){
System.out.println("studying" + s);
}

public String sleep(int age){
System.out.println("sleeping" + age);
return "sleeping";
}

新建java文件reflectionTest2.java写入以下代码:

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
package com.mango.reflection;

import java.lang.reflect.Method;

/**
* @Project unSerialize
* @File com.mango.reflection.reflectionTest2.java
* @Author mango
* @Date 2024/7/28 22:41
* @Description
*/

public class reflectionTest2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class c1 = Class.forName("com.mango.serialize.Person");
Method[] methods1 = c1.getDeclaredMethods();
Method[] methods2 = c1.getMethods();

for(Method m : methods1){
System.out.println(m);
}

System.out.println("---------------分割线---------------");

for(Method m : methods2){
System.out.println(m);
}

System.out.println("---------------分割线---------------");

Method method3 = c1.getMethod("study", String.class); //获取public的study方法
System.out.println(method3);
System.out.println("---------------分割线---------------");

Method method4 = c1.getDeclaredMethod("sleep", int.class);
System.out.println(method4);
}
}

运行结果:

image-20240728225102963

4. 获取构造函数Constructor

1
2
3
4
Constructor<?>[] getConstructors() :只返回public构造函数
Constructor<?>[] getDeclaredConstructors() :返回所有构造函数
Constructor<> getConstructor(Class<?>... parameterTypes) : 匹配和参数配型相符的public构造函数
Constructor<> getDeclaredConstructor(Class<?>... parameterTypes) : 匹配和参数配型相符的构造函数

新建一个java文件PersonConstructor.java并编写以下代码内容:

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
package com.mango.entity;

/**
* @Project unSerialize
* @File com.mango.entity.PersonConstructor.java
* @Author mango
* @Date 2024/7/28 22:59
* @Description
*/

public class PersonConstructor {
private String name;
private int age;

private PersonConstructor(int age) {
this.age = age;
}

private PersonConstructor(String name) {
this.name = name;
}

public PersonConstructor(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public String toString() {
return "PersonConstructor{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

新建一个java文件reflectionTest03.java并编写以下代码内容:

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 com.mango.reflection;

import java.lang.reflect.Constructor;

/**
* @Project unSerialize
* @File com.mango.reflection.reflectionTest03.java
* @Author mango
* @Date 2024/7/28 23:04
* @Description
*/

public class reflectionTest03 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class c = Class.forName("com.mango.entity.PersonConstructor");
Constructor[] constructors1 = c.getDeclaredConstructors();
Constructor[] constructors2 = c.getConstructors();
for (Constructor con : constructors1){
System.out.println(con);
}
System.out.println("---------------分割线---------------");
for (Constructor con : constructors2){
System.out.println(con);
}
System.out.println("---------------分割线---------------");

Constructor constructor3 = c.getConstructor(String.class, int.class);
System.out.println(constructor3);
System.out.println("---------------分割线---------------");
Constructor constructor4 = c.getDeclaredConstructor(String.class);
System.out.println(constructor4);
}
}

运行结果:

image-20240728230935998

2.3 反射创建实例对象

反射创建对象,也叫做做反射之后实例化对象,这里用到的是我们之前讲过的 newInstance() 方法

  • 实例化的一般步骤
1
2
Class c = Class.forName("ClassName");	//获取Class对象
Object o = c.newInstance(); // 创建对象实例

这里再介绍以下invoke方法,invoke方法位于 java.lang.reflect.Method 类中,用于执行某个的对象的目标方法。一般会和getMethod方法配合进行调用函数;

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

第一个参数为类的实例,第二个参数为相应函数中的参数

  • obj:从中调用底层方法的对象,必须是实例化对象

  • args: 用于方法的调用,是一个 object 的数组,参数有可能是多

但需要注意的是,invoke 方法第一个参数并不是固定的:

  1. 如果调用这个方法是普通方法,第一个参数就是类对象;
  2. 如果调用这个方法是静态方法,第一个参数就是类;

首先在Person.java中加入方法instance

1
2
3
public void instance(){
System.out.println("newInstance!");
}

然后我们重新创建一个文件reflectionTest04.java编辑代码举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.mango.reflection;

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

/**
* @Project unSerialize
* @File com.mango.reflection.reflectionTest04.java
* @Author mango
* @Date 2024/7/29 17:34
* @Description
*/

public class reflectionTest04 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Class c = Class.forName("com.mango.entity.Person");
Object o = c.newInstance();
Method method = c.getMethod("instance");
method.invoke(o);
}
}

运行结果:

image-20240729174035334

其实讲到这里应该也能够理解上一节中所讲述的通过反射修改对象值的内容

三、利用反射弹计算器

在讲述这部分内容之前我们需要对Runtime类进行了解

在Java编程中,Runtime 类提供了一种与Java应用程序的运行环境进行交互的方式。Runtime 类是一个单例类,它封装了应用程序运行时的环境,通过它,开发者可以访问JVM的某些底层特性和功能。以下是 Runtime 类的主要作用和功能:

  1. 执行系统命令

    可以使用 exec 方法来执行操作系统命令,这在需要与系统进程交互时非常有用。

    1
    2
    3
    4
    5
    try {
    Runtime.getRuntime().exec("notepad");
    } catch (IOException e) {
    e.printStackTrace();
    }
  2. 内存管理

  3. 关闭JVM

  4. 添加JVM关闭钩子

我们这里主要运用的是==执行系统命令==的功能,所以这里只对执行系统命令进行举例;

假设我们需要使用Runtime类进行弹计算器,那么我们正常通过代码编写应该是

1
2
3
public static void main(String[] args) throws IOException {
Runtime.getRuntime().exec("calc");
}

我们来到java.lang.Runtime中发现Runtime()构造方法是私有的,所以我们不能直接通过newInstance去实例化对象,所以引申出以下两种方法调用exec方法

image-20240730133122979

方法一:通过获取构造方法设置可访问属性setAccessible(true),然后再实例化对象即可,具体实现如下:

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
package com.mango.reflection;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* @Project unSerialize
* @File com.mango.reflection.reflectionRuntime.java
* @Author mango
* @Date 2024/7/30 13:24
* @Description
*/

public class reflectionRuntime {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException {
Class c = Class.forName("java.lang.Runtime");
Constructor con = c.getDeclaredConstructor();
con.setAccessible(true);
Object o = con.newInstance();
Method mRuntime = c.getDeclaredMethod("getRuntime");
Method mExec = c.getDeclaredMethod("exec", String.class);
Object re = mRuntime.invoke(o);
mExec.invoke(re, "calc");
}
}

方法二:使用单例模式直接调用getRuntimeexec函数

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
package com.mango.reflection;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* @Project unSerialize
* @File com.mango.reflection.reflectionRuntime.java
* @Author mango
* @Date 2024/7/30 13:24
* @Description
*/

public class reflectionRuntime {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException {
Class c = Class.forName("java.lang.Runtime");
Method mRuntime = c.getMethod("getRuntime");
Method mExec = c.getMethod("exec", String.class);
Object re = mRuntime.invoke(c);
mExec.invoke(re, "calc");
}
}

四、总结

以上两种方式都是可以成功弹出计算器的,本次的博客是对反射机制进行介绍和一些简单的实践,后续可以通过这些内容在反序列化中进行利用;反射只是一种方式,并不能够作为一种攻击手段;后续还需要学习更多关于反序列化的知识和内容;


Java反序列化之Java反射与URLDNS链分析
http://candyb0x.github.io/2024/07/30/Java反序列化之Java反射/
作者
Candy
发布于
2024年7月30日
更新于
2024年7月30日
许可协议