Self-Studying Java Code Auditing: Reflection
0x00 Introduction: This article records and shares the author’s approach to self-studying Java code auditing, making it easier to summarize and reflect on the learning process in the future. If the author ultimately gains meaningful knowledge, the ideas will be reorganized to provide other self-learners with a useful path forward, leading to this series on self-studying Java code auditing. The article begins with P Niu’s discussion of Java security.
Read in Chinese→0x00 Introduction
This is mainly a record of my approach to learning Java code auditing. Since I am studying on my own, it can feel a bit like working in isolation and relying on the “learn by searching Baidu” method. Nevertheless, I want to document and share the process so that I can summarize and reflect on it in the future. If I ultimately manage to learn something, I will reorganize my thoughts and provide other self-learners with a useful path to follow. This is why I created the following series of articles on self-studying Java code auditing.
P Niu mentioned in an article:
A discussion of Java security can begin with deserialization vulnerabilities, while a discussion of deserialization vulnerabilities can begin with reflection.
0x01 Java Reflection
I previously felt that learning Java was rather rigid because I was only looking at it from a programming perspective. PHP supports all kinds of dynamic calls, making evasion extremely convenient. However, I discovered that Java’s “reflection” functionality can also provide certain dynamic features and is similarly flexible.
Therefore, Java reflection (Reflection) is an extremely important dynamic feature of Java.
By using reflection, we can obtain information about any class’s member methods (Methods), member variables (Fields), constructors (Constructors), and more.
We can also dynamically create Java class instances, invoke arbitrary class methods, modify arbitrary class member variable values, and so on.
Put simply, we can use reflection to obtain an object’s class, use that class to obtain all of its methods—including private ones—and then do whatever we want with the methods we obtain?
0x02 Fundamentals
Several extremely important methods in reflection:
1. Obtaining a Class bytecode object:
Java reflection operates on java.lang.Class objects, so we must first find a way to obtain a Class object. We can usually obtain a class’s Class object in the following ways:
-
类名.class, for example:com.zeo.sec.Test.class. If a class has already been loaded, you can directly access its class property. -
Class.forName("com.zeo.sec.Test.class"). If you know the name of a class and want to obtain that class, you can use forName. -
classLoader.loadClass("com.zeo.sec.Test.class"); -
obj.getClass()If an instance obj of a class exists in the current context, we can obtain its class directly through obj.getClass().
Code snippet for obtaining the Runtime class’s Class object, commonly used for code execution:```java String className = “java.lang.Runtime”; Class runtimeClass1 = Class.forName(className); Class runtimeClass2 = java.lang.Runtime.class; Class runtimeClass3 = ClassLoader.getSystemClassLoader().loadClass(className);
### **2. Obtaining the name**
The class name can be obtained through reflection.
```bash
getName()//获取全名 例如:com.test.Demo
getSimpleName()//获取类名 例如:Demo
3. Obtaining constructors
getConstructors()//获取所有公开的构造函数
getConstructor(参数类型)//获取单个公开的构造函数
getDeclaredConstructors()//获取所有构造函数
getDeclaredConstructor(参数类型)//获取一个所有的构造函数
Obtaining all member methods of the current class:
Method[] methods = clazz.getDeclaredMethods()
Obtaining a specified member method of the current class:
Method method = clazz.getDeclaredMethod("方法名");
Method method = clazz.getDeclaredMethod("方法名", 参数类型如String.class,多个参数用","号隔开);
4. Method for instantiating a class object: newInstance()
The purpose of class.newInstance() is to invoke the class’s no-argument constructor. If newInstance repeatedly fails, the possible reasons are:
-
The class you are using does not have a no-argument constructor. This can be addressed with the getConstructor method.
-
The constructor of the class you are using is private. This can be addressed with the getDeclaredConstructor method.
5. Method for invoking and executing a function through reflection: invoke
After obtaining a java.lang.reflect.Method object, we can invoke a class method through the invoke method of Method.
Code snippet for invoking a class method:
method.invoke(方法实例对象, 方法参数值,多个参数值用","隔开);
invoke 的作用是执行方法,它的第一个参数是:
如果这个方法是一个普通方法,那么第一个参数是 类的实例对象
如果这个方法是一个静态方法,那么第一个参数是 类
0x03 Special Cases
How can we instantiate a class through reflection if it has no no-argument constructor and no static method like the one used in the singleton pattern?
Use the reflection method getConstructor.
The parameter accepted by getConstructor is a list of constructor parameter types. Because constructors also support overloading, a constructor can only be uniquely identified using its parameter type list.
After obtaining the constructor, we use newInstance to execute it.
For example, ProcessBuilder is another commonly used way to execute commands. We can use reflection to obtain its constructor and then invoke start() to execute a command.
Can we execute a method or constructor if it is private?
The difference between the ordinary getMethod and getDeclaredMethod methods is:
The getMethod family of methods obtains all public methods in the current class, including methods inherited from parent classes.
The getDeclaredMethod family of methods obtains methods “declared” in the current class—those actually written in that class—including private methods, but excluding methods inherited from parent classes.
Both getDeclaredConstructor and getConstructor can obtain a class constructor. The difference is:
getConstructor cannot obtain private methods, so when obtaining a class constructor, we generally use getDeclaredConstructor. If the constructor has one or more parameters, we should pass an array containing the corresponding parameter types when obtaining it, for example: clazz.getDeclaredConstructor(String.class, String.class).
For example, as mentioned earlier, the constructor of the Runtime class is private, so we need to use Runtime.getRuntime() to obtain an object. In fact, we can also use getDeclaredConstructor directly to obtain this private constructor, instantiate an object, and then execute a command:
// 获取Runtime类对象
Class runtimeClass1 = Class.forName("java.lang.Runtime");
// 获取构造方法
Constructor constructor = runtimeClass1.getDeclaredConstructor();
constructor.setAccessible(true);
// 创建Runtime类示例,等价于 Runtime rt = new Runtime();
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, "UTF-8"));
The process of invoking Runtime through reflection to achieve local command execution is as follows:
- Use reflection to obtain the
Runtimeclass object (Class.forName("java.lang.Runtime")). - Use the Class object of the
Runtimeclass to obtain the no-argument constructor of theRuntimeclass (getDeclaredConstructor()). Because the constructor ofRuntimeisprivate, we cannot invoke it directly, so we need to modify the method’s access permissions through reflection (constructor.setAccessible(true)). - Obtain the
exec(String)method of theRuntimeclass (runtimeClass1.getMethod("exec", String.class);). - Invoke the
exec(String)method (runtimeMethod.invoke(runtimeInstance, cmd)).
References
P Niu’s “Informal Discussion of Java” on Knowledge Planet
Lingtian Laboratory: https://mp.weixin.qq.com/s/SA\_M0yQiCh8nM0qL2xwT-A