title: Java反序列化漏洞(三)—Java反射
date: 2021-06-22 17:04:08
categories: Java安全
Java反射概述
- 功能:动态获取信息以及动态调用对象方法
- 描述:Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。 这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。 反射被视为动态语言的关键。
优点
Java是一个静态语言,它在编译期间就会检查变量的类型,所以在在写程序的时候需要声明所有变量的数据类型,否则编译失败。正是因为是静态语言,所以它无法像动态语言一样通过少量代码实现多数功能;而程序一旦确定后再去改变一些方法(例如实例化对象的更改)还要重新编译,但反射的存在(Class.forName(className)
)可以使Java通过读取配置信息(类的全限定名)动态改变实例。
应用场景
- 动态加载:上个篇章说的JVM会动态加载所需要的类就是这个
- 动态代理: 在切面编程(AOP)中,需要拦截特定的方法,通常,会选择动态代理方式,这也是反射来实现的
- 各种通用框架:例如Spring、Mybatis等等都是通过读取配置文件信息来动态加载不同对象
- ide的代码补全:idea代码补全也是反射的实现
缺点
- 性能开销:这个不必多说吧
- 破坏封装性:Java封装可谓是一大特性了,但是反射会把类内部的属性和方法暴露出来,而且反射调用方法时可以忽略权限检查,这是很危险的
反射漏洞示例
RCE
java.lang.Runtime
直接看代码
package com.yq1ng;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author ying
* @Description
* @create 2021-06-23 4:33 PM
*/
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
// 1. 先找到java.lang.Runtime类
Class<?> runtime = Class.forName("java.lang.Runtime");
System.out.println("通过Class.forName()找到 " + runtime);
// 2. 找exec方法
Method[] runtimeMethods = runtime.getMethods();
for (Method runtimeMethod : runtimeMethods) {
System.out.println(runtimeMethod);
}
// 3. invoke
Object execObj = runtime.getMethod("exec", String.class).invoke(runtime.newInstance(),"pwd");
}
}
报错:Exception in thread "main" java.lang.IllegalAccessException: Class com.yq1ng.Test can not access a member of class java.lang.Runtime with modifiers "private" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
,私有方法不允许访问,去java.lang.Runtime
看看,可以用getRuntime()
package com.yq1ng;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author ying
* @Description
* @create 2021-06-23 5:21 PM
*/
public class exec {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {
Class<?> runTime = Class.forName("java.lang.Runtime");
Method getRuntime= runTime.getMethod("getRuntime");
Method exec = runTime.getMethod("exec", String.class);
Object obj = getRuntime.invoke(null);
// 使用Process获取子进程的各种流
// win平台不能直接执行命令,需要 cmd.exe /c 命令
Process p = (Process) exec.invoke(obj, "cmd.exe /c dir");
InputStream inputStream = p.getInputStream();
InputStreamReader isr = new InputStreamReader(inputStream);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line=br.readLine())!=null){
System.out.println(line);
}
}
}
java.lang.ProcessBuilder
跟进java.lang.Runtime.exec()
方法看怎么实现的
直接调用java.lang.ProcessBuilder.start()
也可
package com .yq1ng.ProcessBuilder;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
/**
* @author ying
* @Description
* @create 2021-06-23 8:01 PM
*/
public class noParameter {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
Class<?> processBuilder = Class.forName("java.lang.ProcessBuilder");
// 由于 ProcessBuilder 构造函数均有参,且 className.newInstance() 没有参数 , 只能调用无参构造函数
// 所以此处用了 getConstructor() 方法来调用有参函数
// 参数类型为数组
// Object obj = processBuilder.getConstructor(List.class).newInstance(Arrays.asList("id"));
// 有参数的话需要将参数装到一个数组实例中
Object arg[] = new Object[]{new String[]{"ls","-al"}};
Object obj = processBuilder.getConstructor(String[].class).newInstance(arg);
Method start = processBuilder.getMethod("start");
Process p = (Process) start.invoke(obj);
InputStream inputStream = p.getInputStream();
InputStreamReader isr = new InputStreamReader(inputStream);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line=br.readLine())!=null){
System.out.println(line);
}
}
}
在win下比较麻烦,这里直接用kali运行了
反射调用私有方法
上面也提到通过反射可以绕过安全检查,调用私有方法。这里就要使用getDeclaredConstructor()
,这个方法能够返回指定参数类型的所有构造方法 . 包括 public
, protected
以及 private
修饰符修饰的
package com.yq1ng;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author ying
* @Description
* @create 2021-06-23 10:50 PM
*/
public class BypassSecurityChecks {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
Class<?> runtime = Class.forName("java.lang.Runtime");
Constructor<?> runtimeDeclaredConstructor = runtime.getDeclaredConstructor();
// setAccessible 设置为 true 会取消Java安全检查,即可以访问到私有方法
runtimeDeclaredConstructor.setAccessible(true);
Method exec = runtime.getMethod("exec", String.class);
Object obj = runtimeDeclaredConstructor.newInstance();
Process p = (Process) exec.invoke(obj, "uname -a");
InputStream inputStream = p.getInputStream();
InputStreamReader isr = new InputStreamReader(inputStream);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line=br.readLine())!=null){
System.out.println(line);
}
}
}
可以看到,提示了非法操作,而且未来版本反射的非法访问要被修复了