Java code audit - 2. Reflection

reference resources:

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

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

brief introduction

Reflection mechanism is not only an important embodiment of the dynamics of java language, but also the soul of the bottom implementation of various java frameworks. Through reflection, we can:

  • Get the member methods, fields, constructors and other information of any class.
  • Dynamically create java class instances, call arbitrary class methods, modify arbitrary class member variable values, etc.

In a word, the behavior of the program at run time is fixed. If you want to change it at run time, you need to use reflection technology.

java reflection plays an important role in writing exploit code, code audit, bypassing RASP method restrictions, etc.

Imagine a scenario where we need to dynamically create class objects according to user input. You might think of such code.

# className is the dynamic parameter entered by the user.
String className = "java.lang.Runtime";
Object object = new className();

But this operation will not work. The static compilation feature of java determines that the compilation cannot pass. This can be achieved with the help of reflection mechanism.

Learn reflection in practice

We take java.lang.Runtime as an example. Because it has an exec method that can execute system commands, we can see that it is called to rce through reflection in many Exps. Here we try to execute system commands through it.

Before entering the code, describe the following basic steps:

  1. Gets the Class object of the target Class to get the constructor of the target Class.

  2. Gets the target class constructor to create the target instance.

    Because the Runtime constructor is private and cannot be called directly, you need to obtain and modify the access permission.

  3. Create a target instance to call a method in the execution class.

  4. Get the method to be executed in the target class and call to execute the method.

  5. Gets the execution output.

// Get Runtime class object
Class runtimeClass1 = Class.forName("java.lang.Runtime");
// Gets the construction method.
Constructor constructor = runtimeClass1.getDeclaredConstructor();
// Because the constructor is private and cannot be called directly, you need to modify the access permission of the method.
// Create a Runtime class example, which is equivalent to Runtime rt = new Runtime();
constructor.setAccessible(true);
Object runtimeInstance = constructor.newInstance();
// Gets the exec(String cmd) method of the Runtime.
Method runtimeMethod = runtimeClass1.getMethod("exec", String.class);
// Call exec method, equivalent to rt.exec(cmd)
Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);
// Get command execution results
InputStream in = process.getInputStream();
// Output command execution results
System.out.println(IOUtils.toString(in, "GBK"));

Get Class object

Java reflection operates on a java.lang.Class object, so we need to find a way to get this object first. Generally, we can get a Class object in the following ways, taking java.lang.Runtime as an example:

String className     = "java.lang.Runtime";
Class  runtimeClass1 = Class.forName(className);
Class  runtimeClass2 = java.lang.Runtime.class;
Class  runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className);
// It can also be obtained through the object instance. getClass(). However, the construction method of java.lang.Runtime class is private, and object instances cannot be created directly through new.
// Class runtimeClass4 = runtimeInstance.getClass();
  • There are some differences in several ways to obtain Class, involving whether to initialize the target Class. See the end of the article for details.
  • If you need to reflect inner classes, there are Special grammar

Get construction method

Because we finally need an object instance to execute the exec function, we need to create an object instance, and because the Runtime construction method is private, we need to use the constructor object to modify the access permissions.

From the Runtime class code comments, we can see that it doesn't want anyone other than itself to create this class instance, so we can't create a new Runtime class instance. With the help of reflection mechanism, we can modify the method access permission, so as to indirectly create the Runtime object.

The following is the related function of the Class object to obtain the construction method.

  • getConstructor and getDeclaredConstructor

    The former can only get the public constructor, while the latter can get all the constructor.

Create class instance

After obtaining the Constructor, we can create class instances through constructor.newInstance().

  • If you do not have access rights, you can use constructor.setAccessible(true) to modify.

Get class method

In order to execute the exec method, we need to get this method.

The following are the related functions of the Class object acquisition method.

  • getMethod and getDeclaredMethod

    The former will return public methods of the current class and inherited public methods, while the latter will return all methods of the current class.

Call class method

After obtaining the java.lang.reflect.Method object, we can call the method through its invoke method.

  • If the static method is called, the instance object needs to pass null
  • If there is no calling permission, you can use method.setAccessible(true) to modify it

Modify member variables of a class

Java reflection can not only obtain the names of all member variables of the class, but also modify the corresponding values regardless of the permission modifier.

  • getField and getDeclaredField

    The former will return the public fields of the current class and inherited public fields, while the latter will return all fields of the current class.

  • If you do not have permission to modify, you can use field.setAccessible(true) to modify.

  • If you modify the variable of the final attribute, you need to Special grammar

other

initialization

Which of the following code blocks will be executed first?

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); 
            // Trigger static {}
            
//            Class.forName(className,false,this.getClass().getClassLoader()); 
            //It doesn't trigger
            
//            Class  runtimeClass2 = TestJNDI.class; 
            //It doesn't trigger
            
//            Class  runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className); 
            // It doesn't trigger
            
//            Class runtimeClass4 = new TestInit().getClass(); 
            // Trigger sequence static {}, {}, public testinit() {}
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  1. About snippets in classes
    • static {} is called when the class is initialized.
    • The {} code will be placed after super() in the constructor, but before the content of the current constructor.
  2. On several methods of obtaining Class objects
    • The second parameter forName controls whether to initialize the class. The default value is true.

Posted by Stryves on Wed, 01 Dec 2021 00:24:47 -0800