title: Java反序列化漏洞(三)—Java反射
date: 2021-06-22 17:04:08
categories: Java安全

Java反射概述

  • 功能:动态获取信息以及动态调用对象方法
  • 描述:Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。 这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。 反射被视为动态语言的关键。

优点

Java是一个静态语言,它在编译期间就会检查变量的类型,所以在在写程序的时候需要声明所有变量的数据类型,否则编译失败。正是因为是静态语言,所以它无法像动态语言一样通过少量代码实现多数功能;而程序一旦确定后再去改变一些方法(例如实例化对象的更改)还要重新编译,但反射的存在(Class.forName(className))可以使Java通过读取配置信息(类的全限定名)动态改变实例。

应用场景

  1. 动态加载:上个篇章说的JVM会动态加载所需要的类就是这个
  2. 动态代理: 在切面编程(AOP)中,需要拦截特定的方法,通常,会选择动态代理方式,这也是反射来实现的
  3. 各种通用框架:例如Spring、Mybatis等等都是通过读取配置文件信息来动态加载不同对象
  4. ide的代码补全:idea代码补全也是反射的实现

缺点

  1. 性能开销:这个不必多说吧
  2. 破坏封装性:Java封装可谓是一大特性了,但是反射会把类内部的属性和方法暴露出来,而且反射调用方法时可以忽略权限检查,这是很危险的

反射漏洞示例

RCE

java.lang.Runtime

直接看代码

  1. package com.yq1ng;
  2. import java.lang.reflect.InvocationTargetException;
  3. import java.lang.reflect.Method;
  4. /**
  5. * @author ying
  6. * @Description
  7. * @create 2021-06-23 4:33 PM
  8. */
  9. public class Test {
  10. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
  11. // 1. 先找到java.lang.Runtime类
  12. Class<?> runtime = Class.forName("java.lang.Runtime");
  13. System.out.println("通过Class.forName()找到 " + runtime);
  14. // 2. 找exec方法
  15. Method[] runtimeMethods = runtime.getMethods();
  16. for (Method runtimeMethod : runtimeMethods) {
  17. System.out.println(runtimeMethod);
  18. }
  19. // 3. invoke
  20. Object execObj = runtime.getMethod("exec", String.class).invoke(runtime.newInstance(),"pwd");
  21. }
  22. }

报错: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()

Java反射 - 图1

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反射 - 图2

java.lang.ProcessBuilder

跟进java.lang.Runtime.exec()方法看怎么实现的

Java反射 - 图3

Java反射 - 图4

直接调用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运行了

Java反射 - 图5

Java反射 - 图6

反射调用私有方法

上面也提到通过反射可以绕过安全检查,调用私有方法。这里就要使用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);
        }
    }
}

Java反射 - 图7

可以看到,提示了非法操作,而且未来版本反射的非法访问要被修复了