Command Injection Vulnerabilities
What is a command injection vulnerability?
Many applications are not designed to be wholly self-contained. They often access external systems as well, including databases, application programming interfaces (APIs) and others.
Some applications are designed to run commands within the terminal of the system that they are running on. For example, a program may wish to list the files within a directory and decides to accomplish it using the ls or dir commands built into the operating system.
This use of the underlying terminal creates risk. If an application uses untrusted user input when defining the low-level commands to be run, it may include a command injection vulnerability. An attacker can exploit this vulnerability to run their own commands on the system.
Examples of command injection vulnerabilities
Most programming languages have functions that provide the option to run commands in the terminal. Two examples of commonly-used languages with this functionality are C++ and Python.
C++ is a general-use programming language with a great deal of built-in functionality. The C++ system function enables a developer to run terminal commands from within a C++ application.
The image above shows sample code for an application that is designed to print the contents of a file on the filesystem. Instead of using file streams, the program uses the system command to call the cat function with a user-provided filename.
Ideally, a user will enter a legitimate filename, resulting in the contents of the desired file being printed. If the file does not exist, nothing bad will happen either.
However, this program can be abused in a number of different ways. A few examples include:
- Reading unauthorized files: The application is designed to allow users to read files within a given directory. However, by using directory traversal (such as the use of ..), the user can view any file on the system that the application has access to
- Editing files: The use of output redirection (> or >>) could allow a user to redirect the result of printing one file to overwrite or append to another file. Depending on the user’s control of the original file and the application’s permissions levels, this could allow an attacker to add additional user accounts to the system (by editing /etc/shadow) or taking similar malicious actions
- Running unauthorized commands: The Linux and Windows terminals both support command chaining. On Linux, providing an input of filename.txt; netstat would print the contents of the file named filename.txt, then print information about the machine’s current network connections
Python is one of the most popular programming languages in existence. It provides developers with access to the terminal via its subprocess library.
The application above is designed to take advantage of the grep command on the Linux terminal. grep allows files in a directory to be searched for data that matches a particular regular expression. The use of the -r flag, as in the code sample above, performs this search recursively within a given directory.
The sample code above is designed to allow the user to provide a directory and a search pattern and to return any results that match that pattern within the defined directory tree. It accomplishes this using the call function from the Python subprocess library.
As in the previous example, the use of the Linux terminal combined with untrusted user input means that this application is vulnerable to command injection. A malicious user could provide malicious input for the pattern and/or dir variables that is designed to terminate the command and run another or to redirect the output of the grep command to another file. Depending on the permissions associated with this application, this could allow an attacker to abuse the application in a number of different ways.
Remediating command injection vulnerabilities
Command injection vulnerabilities exist when an application makes use of the Linux or Windows terminal or a similar external resource. If untrusted user input is used in terminal commands, there is the potential that a malicious user could subvert a command or run their own commands in the terminal.
The simplest way to remediate command injection vulnerabilities is to not use the terminal at all. Most uses of the terminal could also be accomplished with library functions with much less risk. Whenever possible, terminal access should be removed from an application.
If access to the terminal is necessary within an application and it must use untrusted user input, then input sanitization is necessary. Blocking or escaping common terminal control characters, while not a perfect solution, can decrease the exploitability of command injection vulnerabilities.