The Runtime.getRuntime().exec() method in Java allows execution of system commends or scripts. However, if command arguments are derived from external, untrusted input, this can introduce a command injection vulnerability. An attacker could manipulate the input to execute arbitrary, potentially harmful commands on the host system.
To mitigate this risk, input must be rigorously validated and encoded before being past to the command interpreter. The OWASP Enterprise Security API (ESAPI) library provides utilities for this purpose.
Add the ESAPI dependency to your project:
<dependency>
<groupId>org.owasp.esapi</groupId>
<artifactId>esapi</artifactId>
<version>2.5.1.0</version>
</dependency>
ESAPI requires configuration files. Download ESAPI.properties, validation.properties, and esapi-java-logging.properties from the official repository and place them in your application's resource directory (e.g., src/main/resources).
Below is an example class demonstrating secure command execution for Windows systems. It encodes only the user-provided argument, not the entire command string.
import org.owasp.esapi.codecs.WindowsCodec;
import org.owasp.esapi.Encoder;
import org.owasp.esapi.reference.DefaultEncoder;
import java.io.IOException;
public class SecureCommandExecutor {
private static final String SHELL = "cmd.exe";
private static final String SHELL_OPTION = "/c";
private static final Encoder ENCODER = DefaultEncoder.getInstance();
public static int runSecureCommand(String userInput) throws IOException, InterruptedException {
// Encode the untrusted user input for the Windows command shell
String sanitizedInput = ENCODER.encodeForOS(new WindowsCodec(), userInput);
System.out.println("Sanitized command segment: " + sanitizedInput);
// Construct the full command. Only the user input part is encoded.
String fullCommand = SHELL + " " + SHELL_OPTION + " " + sanitizedInput;
Process proc = Runtime.getRuntime().exec(fullCommand);
return proc.waitFor();
}
}
When calling runSecureCommand("bin\\test.bat"), the encoder escapes special characters. The output might show bin^\test^.bat, where ^ is the escape character for Windows batch files. This prevents the input from breaking out of the intended command context.
Critical Consideratino: Encode only the dynamic, user-supplied portions of the command. Encoding the entire command string, including the static shell executable (cmd.exe), will render it unexecutable, as demonstrated below:
// Incorrect: Encoding the entire command string
String faultyCommand = "cmd.exe /c bin\\test.bat";
String encodedFaultyCommand = ENCODER.encodeForOS(new WindowsCodec(), faultyCommand);
// Result: "cmd^.exe^ ^/c^ bin^\test^.bat" - This will fail to execute.