Java reflection mechanism and security issues

Java reflection mechanism and security issues

0x00 Foreword

Recently, in the process of summarizing the Java deserialization vulnerability, the author found a word "Java reflection mechanism" that can't be circumvented. In the past, I only knew that this is a mechanism for implementing Java "quasi-dynamic" language, which is convenient for developers to debug programs However, I did not expect that "reflection" has become one of the methods of deserialization vulnerability attacks, so in this article, I will summarize the principles of the reflection mechanism and what other security issues exist.

Reflex mechanism mind map:
Insert picture description here

0x01 Java reflection mechanism definition

Java reflection mechanism means that in the running state, for any class, you can know all the properties and methods of this class; for any object, you can call any of its methods and properties; this dynamically obtained information and dynamic The function of calling the method of the object is called the reflection mechanism of java language.

The important point of the reflection mechanism is "runtime", which allows us to load, explore, and use .class files that are completely unknown during compilation while the program is running. To sum up in one sentence, reflection can realize the properties and methods of any class at runtime.

0x02 advantages and disadvantages of the reflection mechanism

We may have a question when we are new to reflection. Why use reflection to create objects directly? Isn't it fragrant? This time involves the concepts of dynamic compilation and static compilation in Java.

  • Static compilation: Determine the type at compile time and bind the object.
  • Dynamic compilation: determine the type at runtime and bind the object. It maximizes the flexibility of Java, reflects polymorphism, and reduces the coupling between classes.

Advantages: The
reflection mechanism can realize the dynamic creation of objects and compilation, showing a lot of flexibility, especially in the J2EE developer his flexibility is very obvious. For example, in the development of a large-scale software, when the program is compiled and released, if we need to update some functions in the future, we cannot ask the user to uninstall the previous software and then reinstall the new version. If it is static, the entire program needs to be recompiled once to achieve the function update, and if the reflection mechanism is used, it can be achieved without dynamic uninstallation, and only needs to be dynamically created and compiled at runtime.

Disadvantages:
have an impact on performance. The reflection mechanism is actually an explanatory operation. We tell the JVM what we want to do and how they group our requirements. This type of operation is always slower than just performing the same operation directly

0x03 Principle of reflection mechanism

The principle basis of the reflection mechanism is to understand the Class class, which is an instance object of the java.lang.Class class, and Class is the class of all classes. For ordinary objects, we usually use the following methods when creating instances:

Demo test = new Demo();

So can we also use the above method to create an instance object of class class?

Class c = new Class();

The answer is not good, so we look at the source code of Class and find that his constructor is private, which means that only the JVM can create Class objects.
Insert picture description here

Although we cannot use new to instantiate an object like creating an ordinary object, we can get a Class object by randomizing an existing class. There are three ways, as follows: (assuming that there is a common class called Demo)

  1. Instantiate an ordinary other class, and then call getClass () on this instance to get the Class object
Demo test = new Demo();
Class c1 = test.getClass();
  1. Any data type (including basic data types) has a "static" class attribute, so directly call the .class attribute to get the Class object
Class c2 = Demo.class;
  1. Call the forNmae method of the Class class to obtain the Class object
Class c3 = Class.forName(“ReflectDemo.Demo”)//forName()

The parameter is the real path, package name, class name
Insert picture description here
Insert picture description here

Of the three creation methods, the third one is usually used, because the first one has created objects and has lost the meaning of the reflection mechanism; the second one needs to import the class package, the dependency is too strong, and the package will be thrown without import Compile Error. The third method is generally used. A string can be passed in or written in the configuration file.

The principle of the reflection mechanism is to map various components in the java class into individual java objects, so we can call all members (variables, methods) in the class at runtime. The following figure is the loading process of classes in the reflection mechanism:
Insert picture description here

0x04 Java reflection mechanism operation

Through the previous introduction, we learned how to obtain the Class object, and through the reflection mechanism, you can get all the member information of the Class object, then we briefly introduce some functions to obtain members:

  • Get member method
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) 

// Get all the methods of this class, excluding the parent class.

public Method getMethod(String name, Class<?>... parameterTypes) 

// Get all the public methods of this class, including the parent class.

The two parameters are the method name and the class parameter list of the method parameter class (class type).
If there are four member methods in class A, as follows:
Insert picture description here
use the getDeclaredMethod () and getMethod () functions to obtain all the / public member methods in the specified class
Insert picture description here
Insert picture description here

  • Get constructor
`public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)` 

// Get all the constructors of this class, excluding the constructor of its parent class.

public Constructor<T> getConstructor(Class<?>... parameterTypes) 

// Get all public constructors of this class, including the parent class.

If there are three constructors in class A, two public and one private constructor
Insert picture description here
use getDeclaredConstructor () function to get all constructors; use getConstructor () function to get only public constructors.
Insert picture description here
Insert picture description here

  • Get member variable Field
public Field getDeclaredField(String name) 

// Get all the variables declared by the class itself, excluding the variables of its parent class.

public Field getField(String name)

// Obtain all public member variables of this class, including its parent class variables.

The member variable of the class is also an object, which is an object of java.lang.reflect.Field, so we obtain this information through the method encapsulated in java.lang.reflect.Field.
There are some member variables
Insert picture description here
with different attributes in class A: use getDeclaredFields () function to get all member variables; use getFields () function to get only public member variables.
Insert picture description here
Insert picture description here

0x05 reflection mechanism and deserialization vulnerability

The key function of the deserialization vulnerability:
writeObject () serialize, output Object to Byte stream
readObject () deserialize, output Byte stream to Object

Using the reflection mechanism, rewrite the readObject method, add the function Runtime.getRuntime () that can perform command execution, execute the calc.exe command to call up the calculator

package reflectdemo;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;

public class demo implements Serializable{
	private Integer age;
	private String name;
    public demo() {}
    public demo(String name,Integer age){ //构造函数,初始化时执行
    	this.age = age;
    	this.name = name;
    } 
    private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException{
    	in.defaultReadObject();//调用原始的readOject方法
    	try {//通过反射方法执行命令;
    	Method method= java.lang.Runtime.class.getMethod("exec", String.class);
    	Object result = method.invoke(Runtime.getRuntime(), "calc.exe");    
    	}
    	catch(Exception e) {
    		e.printStackTrace();
    	}
    }
	public static void main(String[] args){
		demo x= new demo();
		operation.ser(x);
		operation.deser();
	}
}
class operation {
	public static void ser(Object obj) {
		try{//序列化操作,写数据
	        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.obj"));
	        //ObjectOutputStream能把Object输出成Byte流
	        oos.writeObject(obj);//序列化关键函数
	        oos.flush();  //缓冲流 
	        oos.close(); //关闭流
	    } catch (FileNotFoundException e) {        
	        e.printStackTrace();
	    } catch (IOException e) {
	        e.printStackTrace();
	    }
	}
	public static void deser() {
		try {//反序列化操作,读取数据
			File file = new File("object.obj");
			ObjectInputStream ois= new ObjectInputStream(new FileInputStream(file));
			Object x = ois.readObject();//反序列化的关键函数
			System.out.print(x);
			ois.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

From the above deserialization vulnerability, it can be seen that Java reflection can indeed access private methods and properties, which is a way to bypass the second-level security mechanism (one). It is actually something similar to a "backdoor" left by Java itself for a certain purpose, or to facilitate debugging. In any case, its principle is to turn off access security checks.
Insert picture description here

In general, when we find a vulnerability and want the program to implement command execution, there are two directions we can work hard

  1. Control codes and functions: data is treated as code like injection holes such as named injection; or readObject is rewritten like the above demo code, and custom code is added
  2. Control input, data, variables: use existing functions and logic in the code to control the process by changing the form of the input content (different inputs will take different logic flows and execute the code in different code blocks)

For Java deserialization vulnerabilities, this belongs to the category of control data entry. When calling the reflection mechanism to trigger a vulnerability, he has two basic points that must be met:

  1. There is a serializable class, and the class has rewritten the readObject () method (since there is no code injection, you can only find if there is such a class in the existing code logic)
  2. The method.invoke function appears in the logic of the rewritten readObject () method, and the parameters are controllable.

0x06 reflection safety

After reading the above, we should sincerely sigh that the Java reflection mechanism is too powerful. However, if we have a certain sense of security, we will find that the Java mechanism is too powerful.

Security is a more complex issue when dealing with reflections. Reflection is often used by framework-type code. Because of this, we may want the framework to have full access to the code, regardless of the conventional access restrictions. However, in other cases, uncontrolled access can pose serious security risks.

Because of these conflicting requirements, the Java programming language defines a multi-level approach to deal with the security of reflection. The basic mode is to implement the same restrictions on reflection as applied to source code access:

  1. Access from any location to common components
  2. No private component access outside the class itself
  3. Limited access to protected and packaged (default access) components

Compared to C ++, Java is a relatively safe language. This is closely related to their operating mechanism. C ++ runs locally, which means that the permissions of almost all programs are theoretically the same. Since Java is running in a virtual machine and does not directly contact the outside world, the running environment of Java is actually a "sandbox" environment. And as the security model of Java, it includes: a series of security components such as bytecode verifier, class loader, security manager, access controller, etc., so the security mechanism of Java seems to be more complicated.

Published 21 original articles · won 14 · visited 4075

Guess you like

Origin blog.csdn.net/m0_38103658/article/details/105482035