• Java 代码审计 — 2. Reflection


    参考:

    https://zhishihezi.net/b/5d644b6f81cbc9e40460fe7eea3c7925

    https://stackoverflow.com/questions/16966629/what-is-the-difference-between-getfields-and-getdeclaredfields-in-java-reflectio

    简介

    反射机制是 java 语言的动态性的重要体现,也是 java 的各种框架底层实现的灵魂。通过反射我们可以:

    • 获取到任何类的成员方法 (Methods)、成员变量 (Fields)、构造方法 (Constructors) 等信息。
    • 动态创建 java 类实例、调用任意的类方法、修改任意的类成员变量值等。

    总而言之,程序在运行时的行为是固定的,如果想在运行时改变,就需要用到反射技术。

    java 反射在编写漏洞利用代码、代码审计、绕过 RASP 方法限制等中起到了至关重要的作用。

    假想一个场景,如果我们需要根据用户输入来动态的创建类对象。可能会想到这样的代码。

    # className 为用户输入的动态参数。
    String className = "java.lang.Runtime";
    Object object = new className();
    

    但这个操作是不行的,java 静态编译特性决定了编译无法通过。而借助反射机制可以完成这个目的。

    练习中学习反射

    我们以 java.lang.Runtime 为例,因为它有一个 exec 方法可以执行系统命令,所以在很多 exp 中都能看到通过反射调用它来 rce 。这块我们就尝试通过它来执行系统命令。

    在进入代码之前,介绍下基本步骤:

    1. 获取目标类 Class 对象,以便获取目标类的构造方法。

    2. 获取目标类构造方法,以便创建目标实例。

      因为 Runtime 构造方法是 private 的,无法直接调用,所以需要获取到修改一下访问权限。

    3. 创建目标实例,以便调用执行类中的方法。

    4. 获取目标类中要执行的方法,并调用执行该方法。

    5. 获取执行输出。

    // 获取Runtime类对象
    Class runtimeClass1 = Class.forName("java.lang.Runtime");
    // 获取构造方法。
    Constructor constructor = runtimeClass1.getDeclaredConstructor();
    // 因为构造方法是 private 的,无法直接调用,所以需要修改方法的访问权限。
    // 创建Runtime类示例,等价于 Runtime rt = new Runtime();
    constructor.setAccessible(true);
    Object runtimeInstance = constructor.newInstance();
    // 获取Runtime的exec(String cmd)方法。
    Method runtimeMethod = runtimeClass1.getMethod("exec", String.class);
    // 调用exec方法,等价于 rt.exec(cmd)
    Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);
    // 获取命令执行结果
    InputStream in = process.getInputStream();
    // 输出命令执行结果
    System.out.println(IOUtils.toString(in, "GBK"));
    

    获取 Class 对象

    java 反射操作的是 java.lang.Class 对象,所以我们需要先想办法获取到这个对象,通常我们有如下几种方式获取一个类的 Class 对象,以 java.lang.Runtime 为例:

    String className     = "java.lang.Runtime";
    Class  runtimeClass1 = Class.forName(className);
    Class  runtimeClass2 = java.lang.Runtime.class;
    Class  runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className);
    // 通常也可以通过 对象实例.getClass() 这种方式获取。但是 java.lang.Runtime 这个类的构造方法是私有的,不能直接通过 new 创建对象实例。
    // Class runtimeClass4 = runtimeInstance.getClass();
    
    • 几个获取 Class 的方式有些区别,涉及是否初始化目标类的问题,详见文章末尾。
    • 如果需要反射内部类,则有 特殊的语法

    获取构造方法

    因为我们最终要执行 exec 函数是需要一个对象实例的,所以我们需要创建一个对象实例,并且由于 Runtime 的构造方法是私有的,所以我们需要使用 constructor 对象来修改访问权限。

    image-20211029181629649

    从 Runtime 类代码注释,可以看到它本身是不希望除了其自身以外的任何人去创建该类实例,因此我们没办法 new 一个 Runtime 类实例。我们可以借助反射机制,修改方法访问权限从而间接的创建出了 Runtime 对象。

    下面是 Class 对象获取构造方法的相关函数。

    image-20211029181600225
    • getConstructorgetDeclaredConstructor

      前者只能获取到公有的构造方法,而后者可以获取到所有构造方法。

    创建类实例

    获取到 Constructor 以后我们可以通过 constructor.newInstance() 来创建类实例。

    image-20211029225537109
    • 若无访问权限,则可以使用 constructor.setAccessible(true) 进行修改。

    获取类方法

    为了执行 exec 这个方法,我们需要获取到这个方法。

    下面是 Class 对象获取方法的相关函数。

    image-20211029181725007
    • getMethodgetDeclaredMethod

      前者会返回当前类公有方法和继承的公有方法,而后者会返回当前类所有方法。

    调用类方法

    获取到 java.lang.reflect.Method 对象后,我们可以通过其 invoke 方法来调用该方法。

    image-20211029230048772
    • 如果调用的是 static 方法,则实例对象需要传入 null
    • 若无调用权限,则可以使用 method.setAccessible(true) 进行修改

    修改类的成员变量

    Java 反射不但可以获取类所有的成员变量名称,还可以无视权限修饰符实现修改对应的值。

    image-20211029181828901
    • getFieldgetDeclaredField

      前者会返回当前类公有字段和继承的公有字段,而后者会返回当前类所有字段。

    • 若无修改权限,则可以使用 field.setAccessible(true) 进行修改。

    • 若修改 final 属性的变量,则需要 特殊的语法

    其它

    初始化

    以下顺序的代码块,哪个会先执行呢?

    import org.junit.Test;
    
    class TestInit{
        {
            System.out.println("{}");
        }
        static {
            System.out.println("static{}");
        }
        public TestInit(){
            super();
            System.out.println("public TestInit(){}");
        }
    }
    
    public class TestXXX {
    
        @Test
        public void test1(){
            try {
                String className = "JNDI.TestInit";
                
                Class.forName(className); 
                // 触发 static{}
                
    //            Class.forName(className,false,this.getClass().getClassLoader()); 
                //都不触发
                
    //            Class  runtimeClass2 = TestJNDI.class; 
                //都不触发
                
    //            Class  runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className); 
                // 都不触发
                
    //            Class runtimeClass4 = new TestInit().getClass(); 
                // 触发顺序 static{} , {} , public TestInit(){}
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    1. 关于类中的代码段
      • static{} 是在类初始化时调用的。
      • {} 代码会放在构造函数中 super() 后,但当前构造函数内容前。
    2. 关于几种获取 Class 对象方法
      • forName 第二个参数就是控制是否执行类的初始化,默认为 true。
    image-20211115112218863
  • 相关阅读:
    LightOJ
    Peter and Snow Blower
    Gena's Code
    nyoj139--我排第几个 (康拓展开)
    hdoj1394(归并排序)
    树状数组
    Poj 1113--Wall(凸集)
    hdoj1437 -- 天气情况
    hdoj1428 -- 漫步校园 (记忆化搜索)
    图像边缘检测
  • 原文地址:https://www.cnblogs.com/starrys/p/15612800.html
Copyright © 2020-2023  润新知