java_ Command execution

Keywords: security Web Security security hole

java_ Command execution

Runtime class analysis

First look at the demo of a command execution, as follows

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class RuntimeTest {
    public static void main(String[] args) throws IOException {
        Runtime runtime = Runtime.getRuntime();
        Process result = runtime.exec("whoami");
        InputStream in = result.getInputStream();
        byte[] bytes = new byte[1024];
        int readsize = 0;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        while ((readsize = in.read(bytes)) != -1){
            out.write(bytes,0,readsize);
        }
        System.out.println(out.toString());
    }
}

Obviously, the system commands are executed through the Runtime::exec method. Following up the runtime class, it is found that there are the following six exec overloaded methods

public Process exec(String command)
public Process exec(String command, String[] envp)
public Process exec(String command, String[] envp, File dir)
public Process exec(String cmdarray[])
public Process exec(String[] cmdarray, String[] envp)
public Process exec(String[] cmdarray, String[] envp, File dir)

The six exec methods differ as follows:

  1. The first overload means that the specified string command is executed in a separate process
  2. The second overload means that the specified string command is executed in a separate process in the specified environment
  3. The third overload means that the specified string command is executed in a separate process in the specified environment and working directory
  4. The first overload means that the specified string commands and variables are executed in a separate process
  5. The second overload represents the execution of the specified string commands and variables in a separate process in the specified environment
  6. The third overload means that the specified string commands and variables are executed in a separate process of the specified environment and working directory

Call chain analysis

Follow up the exec method and find the exec method that calls its overload

Following up, this method calls the overloaded exec method again

Continue to follow up. The exec call chain of the following method ends. You will find that no matter which overloaded exec method is called at the beginning, it will eventually call the following exec method

This method calls the ProcessBuilder::start method. It is found that it calls the ProcessImpl::start method

The call chain is Runtime:: exec - > processbuilder:: exec - > processimpl:: exec

Execute command code according to three types of call chain

According to the above call chain analysis, there are three command execution methods: Runtime, ProcessBuilder and ProcessImpl

Runtime execute command

The Runtime can have the following three

  1. The first is the demo above
  2. The second is to execute through reflection and obtain the Runtime class instance through the getRuntime method. Why do you want to obtain it through the getRuntime method? Because the Runtime constructor is private and cannot be instantiated externally, it returns the Runtime object through a static method, which ensures that there is only one instance of the Runtime object! This is actually the singleton mode

    Why design like this? Borrow a passage from God P

    Some students are curious about why class construction methods are private. Don't they want users to use this class? This actually involves a very common design pattern: "Singleton pattern". (sometimes the factory model is written similarly)

    For example, for Web applications, the database connection only needs to be established once, rather than a new connection every time the database is used. At this time, as a developer, you can set the constructor of the class used for the database connection to private, and then write a static method to obtain it
    The execution command code is as follows

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.reflect.Method;

public class Demo02 {
    public static void main(String[] args) throws Exception{
        Class clazz = Class.forName("java.lang.Runtime");
        Method getRuntimeMethod = clazz.getMethod("getRuntime");
        Method execMethod = clazz.getMethod("exec", String.class);
        Object runtimeObject = getRuntimeMethod.invoke(Runtime.class);
        Process process = (Process)execMethod.invoke(runtimeObject, "whoami");
        InputStream inputStream = process.getInputStream();
        byte[] bytes = new byte[1024];
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        int readsize = 0;
        while ((readsize = inputStream.read(bytes)) != -1){
            byteArrayOutputStream.write(bytes,0,readsize);
        }
        System.out.println(byteArrayOutputStream.toString());
    }
}

There may be a question here, which is why the execution of getRuntimeMethod method should pass in a class object instead of the object executing the method? In fact, you can compare the static method call of java with such a class name and static method name, so it is not difficult to understand when compared with reflection!
3. The third is to obtain the Runtime object through the constructor, but the Runtime constructor is private. You can obtain the private constructor through the getdeclaraedconstructor method

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class Demo03 {
    public static void main(String[] args) throws Exception{
        Class clazz = Class.forName("java.lang.Runtime");
        Constructor declaredConstructor = clazz.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        Object runtimeObject = declaredConstructor.newInstance();
        Method execMethod = clazz.getMethod("exec", String.class);
        Process process = (Process)execMethod.invoke(runtimeObject, "whoami");
        InputStream inputStream = process.getInputStream();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] bytes = new byte[1024];
        int readsize = 0;
        while ((readsize = inputStream.read(bytes)) != -1){
            byteArrayOutputStream.write(bytes,0,readsize);
        }
        System.out.println(byteArrayOutputStream.toString());
    }
}

It should be noted here that the obtained construction method is private, so you need to turn off the java access language check through the setAccessible method, otherwise it cannot be instantiated

ProcessBuilder

View the exec method that Runtime calls last, calling ProcessBuilder::start to execute the command, then directly reflect the execution of this method.

Note that the newInstance method originally receives the adjustable side length parameter (that is, array), and the passed in is also an array, so it will be superimposed into a two-dimensional array

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class Demo04 {
    public static void main(String[] args) throws Exception{
        Class clazz = Class.forName("java.lang.ProcessBuilder");
        Constructor constructor = clazz.getConstructor(String[].class);
        Method startMethod = clazz.getMethod("start");
        Process process = (Process)startMethod.invoke(constructor.newInstance(new String[][]{{"whoami"}}));
        InputStream inputStream = process.getInputStream();
        byte[] bytes = new byte[1024];
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        int readsize = 0;
        while ((readsize = inputStream.read(bytes)) != -1){
            byteArrayOutputStream.write(bytes,0,readsize);
        }
        System.out.println(byteArrayOutputStream.toString());
    }
}

ProcessImpl

Follow up the ProcessBuilder::start method, which calls the ProcessImpl.start method for command execution. Then directly reflect and execute this method

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Map;

public class Demo05 {
    public static void main(String[] args) throws Exception{
        Class clazz = Class.forName("java.lang.ProcessImpl");
        Method startMethod = clazz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);
        startMethod.setAccessible(true);
        Process process = (Process)startMethod.invoke(clazz, new String[]{"whoami"}, null, null, null, false);
        InputStream inputStream = process.getInputStream();
        byte[] bytes = new byte[1024];
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        int readsize = 0;
        while ((readsize = inputStream.read(bytes)) != -1){
            byteArrayOutputStream.write(bytes,0,readsize);
        }
        System.out.println(byteArrayOutputStream.toString());
    }
}

Note that neither the ProcessImpl class nor its start method has an access modifier, so it is the default. In Java, it is accessible under the same package by default, that is, under the java.lang package. Therefore, you need to turn off the Java access language check through setAccessible.

Posted by ade1982 on Sun, 28 Nov 2021 09:11:30 -0800