Injections are one of the most common vulnerabilities in applications. Depending on what environment and utilities you use, there can be a variety of injection flaws. Among these types, command injection is one of the most dangerous. The impact of command injection can range from stealing data, changing system configurations, or even bringing the whole system down. Malicious actors sometimes use command injection to create security weaknesses in the system and then exploit the newly created weaknesses.
It’s crucial to secure applications from vulnerabilities. And Java is one of the most popular programming languages for application development. So, in this post, let’s see what command injection is and how it works in Java and, finally, understand how we can prevent command injection vulnerabilities.
What Is Command Injection?
Command injection is a technique where a malicious actor tries to execute OS commands on the system hosting the application. Some features might need you to use system commands. And in some cases, user input is part of these commands. For example, you could be storing users’ data through your application in a folder named after the username. You could be using system commands to check if a user’s directory exists and then show the files in that directory.
In such cases, you’re using some information provided by the user (the username) and using it in system commands. This creates a possibility for command injection vulnerabilities. For a command injection attack to work, the application should meet three main conditions:
The application should have privileges/permissions to execute system commands.
The application should use user-provided data as a part of system commands.
The user-provided data should not be escaped/sanitized before use.
If your application meets these conditions, then there’s a pretty good chance that your application is vulnerable to command injections. You can’t always trust user input. So, let’s take the above example where you’re storing user data in a folder. If the user enters a valid username, then it’s all good. The application executes the command and everything goes as planned. But if a malicious user gives an injection as the input, then there’s a problem.
To understand command injection better, let’s take an example and see how it works.
How Does Command Injection Work?
Here’s a Java program that meets all three conditions mentioned above:
import java.util.*;
import java.io.File;
import java.io.*;
public class command_injection{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
System.out.println("Enter Username");
String user = sc.nextLine();
String user_path = ".\\Data\\"+user;
File file = new File(user_path);
try {
String comm = "cmd.exe /c dir "+user_path;
Process process = Runtime.getRuntime().exec(comm);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
String s = null;
while ((s = stdInput.readLine()) != null) {
System.out.println(s);
}
}
catch (IOException e) {
System.out.println("Error executing command");
}
}
}
The above code asks the user for a username. When the user enters the username, the code checks if there’s a folder that matches the username. If yes, it uses a system command to display the contents of the folder; if not, it displays an error message. I’ve created a folder named “Tony” and created a few files in it.
First, let’s see what happens if we give valid input:
When I give “Tony” as input, the application generates the following line:
cmd.exe /c dir ./Data/Tony
cmd.exe is the command prompt, and it executes the command dir ./Data/Tony. This shows us all the files in the folder named “Tony”. Now, what if along with the username, we added something malicious. Let’s say I give the following input as the username:
Tony & ping -n 2 8.8.8.8
Based on this input, the application generates the following command to execute:
dir ./Data/Tony & ping -n 2 8.8.8.8
& is used to run multiple commands in a single line. When the above is executed, it first checks for a folder named “Tony”. And because we have that folder, it will display the contents of that folder. But the command doesn’t end there. The next part of the command is pinging to an IP. Now, the application also executes this part of the command:
This is how command injections work. Malicious actors craft input such that it manipulates the original function of the application.
What if the injection was more dangerous than just pinging to an IP—like, “shut down the system” or “delete an important file”? The result would be catastrophic.
Dangerous Payloads
Here’s an example of a payload for Windows to delete a folder:
Tony & dir & rmdir /Q /S Important & dir
This payload should delete the folder named Important. I’m using the dir command to display the contents of the folder before and after deletion.
Another example is if the input is Tony & shutdown /s. The command would be:
dir ./Data/Tony & shutdown /s
If the application executes this command, the command prompt would first display the contents of the file and then shut down the system. When this command is executed on the server, the whole application could go down.
Here are some examples of dangerous commands for Windows and Linux-based operating systems. NOTE: Do not run these commands unless you know what you’re doing!
Sounds scary, doesn’t it? But don’t worry, we can protect ourselves from command injections.
Preventing Command Injection Attacks
There’s no master key to prevent command injections. It means that you can’t just implement one thing and expect to be secure. You need to add multiple layers of security. When it comes to security, the more, the better. So, here are some common and useful prevention methods.
If you’re not using system commands, then command injections aren’t possible.
Do You Need System Commands?
This is the first question you need to ask yourself. If you’re not using system commands, then command injections aren’t possible. So, the first step is to avoid using system commands.
The next question to ask yourself is, “Are there any alternatives?” In some cases, you have to use system functions. There’s no workaround. But you don’t necessarily need to use system commands. Java offers a wide range of libraries and functions that do the same task as system commands. So, you’d still be doing the same thing but not by directly executing system commands.
Let’s take an example of the use-case discussed above. You have to check if a user’s directory exists, and if it does, show the files in that directory. The following code has two sections: first, where we use a library, and second, where we’re using direct system commands on the command line.
import java.util.*;
import java.io.File;
import java.io.*;
import java.nio.file.Files;
public class a{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
System.out.println("Enter Username");
String user = sc.nextLine();
String user_path = ".\\Data\\"+user;
File file = new File(user_path);
try {
System.out.println("\n****** Output from library ******\n");
Files.list(new File(user_path).toPath())
.limit(10)
.forEach(path -> {
System.out.println(path);
});
}
catch (IOException e) {
System.out.println("Error executing command");
}
try {
String comm = "cmd.exe /c dir "+user_path;
System.out.println("\n****** Output from command line ******\n");
Process process = Runtime.getRuntime().exec(comm);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
String s = null;
while ((s = stdInput.readLine()) != null) {
System.out.println(s);
}
}
catch (IOException e) {
System.out.println("Error executing command");
}
}
}
When you give a valid input—i.e., Tony.—you get proper results from both approaches.
But when you give a malicious payload as input, the library function throws an error. Whereas the application executes the payload when using system commands.
The advantage of using such libraries over system commands is that most of these libraries have considered the security aspects and have implemented security measures and checks.
Escaping Input
In most cases, for a command injection to work, a user needs to use multiple commands in a single line. And these commands are delimited by certain characters. In the above example, we saw how the character & (ampersand) was used to run two commands. But that’s not it—there are more such characters. Some common characters are ; (semicolon) and | (pipeline). There’s also backtick (`), which has a special meaning. When a command is enclosed within backticks, that command is evaluated before the main command is executed. For example, if the command looks like:
command_1 `command_2`
Then command_2 is executed first and its output is used in command_1.
You should escape such characters in user input. You could validate the input, strip these characters, or parse input with these characters to get the desired data.
Here’s a code snippet to strip and escape these special characters:
Once I generate the command as shown in the code above, I’ll use the following code to strip and escape special characters.
String strip = comm.replaceAll("[;&|`]*","");
System.out.println("Stripped: " + strip);
String escape = comm.replaceAll("[;&|`]+","\\u" + Integer.toHexString('/' | 0x10000).substring(1)); //Unicode
System.out.println("Stripped: " + escape);
Here, you can see that the code strips and escapes the special characters, making the injection useless. The part of the command to ping to 8.8.8.8 is not executed.
You can strip characters when you just want to remove the characters. You can use escaping if you want to further log and analyze inputs. When you log inputs with Unicode data, you can convert the Unicode characters back to string to see the actual input.
Principle of Least Privilege
The principle of least privilege says that you should give an entity the least amount of privilege necessary—just enough to do what’s needed. For example, if the application or user needs access to just one folder, then give access to only that folder. Giving more permissions or superuser privileges is not at all smart. Implement this principle everywhere.
You could also create a separate user for the application and give only required permissions to that user. This might not be feasible everywhere—it depends on the use-case—but you should consider it.
Allowlist and Denylist
If you know what commands must be used, or what commands must not be used, you could allow or block them. If your application needs to execute only one command, then you could use a logic that checks if the command being sent to the command line for execution is that one command you intend to execute. Let’s say you only need to execute the command ls /home/Documents_. Y_ou don’t need to execute any other command. Then you can use a logic similar to the following:
if (command == "ls /home/Documents") {
Process process = Runtime.getRuntime().exec(command);
}
Allow-listing and deny-listing are very useful because they make most of the crafted injections useless. But to implement these, you need to have a complete idea of all the scenarios. If you don’t, it could affect the application’s functioning.
Manual security testing has its perks, but automated security testing makes things really easy.
Security Testing
Nobody is completely secure. Even after spending a lot of time and effort, we can’t be 100% secure. And to know where you could get better, you need security testing. Security testing is a continuous process in application security. You can find new vulnerabilities, new attacks, and new tools almost every day. And you need to keep up with malicious actors.
Manual security testing has its perks, but automated security testing makes things really easy. If you’re looking for such a product, try StackHawk. StackHawk offers automated application security testing on every pull request. It continuously finds API and application vulnerabilities and makes security analysis easy.
This post was written by Omkar Hiremath. Omkar is a cybersecurity analyst who is enthusiastic about cybersecurity, ethical hacking, data science, and Python. He’s a part time bug bounty hunter and is keenly interested in vulnerability and malware analysis.