reference resources:
https://zhishihezi.net/b/5d644b6f81cbc9e40460fe7eea3c7925
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:
-
Gets the Class object of the target Class to get the constructor of the target Class.
-
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.
-
Create a target instance to call a method in the execution class.
-
Get the method to be executed in the target class and call to execute the method.
-
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(); } } }
- 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.
- On several methods of obtaining Class objects
- The second parameter forName controls whether to initialize the class. The default value is true.