Fixing command injection
Web applications such as the ones developed with ASP.NET Core have a plethora of components and libraries that enable them to execute OS commands in the host. If not written securely, the code that composes and runs these commands can likely expose the ASP.NET Core web application to command injection exploitation. Shell commands can be executed unexpectedly if this security flaw in code is not prevented.
In this recipe, we will identify the command injection vulnerability in code and fix the security vulnerability.
Getting ready
Using Visual Studio Code, open the sample Online Banking app folder at Chapter02\command-injection\before\OnlineBankingApp
.
Testing command injection
Here are the steps:
- Navigate to Terminal | New Terminal in the menu or simply press Ctrl + Shift + ' in Visual Studio Code.
- Type the following command in the terminal to build and run the sample app:
dotnet run
- Open a browser and go to
http://localhost:5000/Backups/Create
. - The browser will display the web page for initiating database backup, as shown in the following screenshot:
- Enter this command injection payload,
backup & calc
, in the Backup Name field, and hit the Create button. - Notice that the page redirected to the list of backup pages and the backup was created. However, the calculator app has appeared:
If this security bug is not handled, this problem could also expose the underlying hosts to Remote Code Execution (RCE).
How to do it…
Let's take a look at the steps for this recipe:
- Launch Visual Studio Code and open the starting exercise folder by typing the following command:
code .
- Navigate to Terminal | New Terminal in the menu or simply press Ctrl + Shift + ' in Visual Studio Code.
- Type the following command in the terminal to build the sample app to confirm that there are no compilation errors:
dotnet build
- Open the
Services/BackupService.cs
file and locate the vulnerable part of the code in theBackupDB(string backupname)
method:public async Task BackupDB(string backupname) { Â Â Â Â using (Process p = new Process()) Â Â Â Â { Â Â Â Â Â Â Â Â string source = Â Â Â Â Â Â Â Â Â Â Â Â Environment.CurrentDirectory + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â "\\OnlineBank.db"; Â Â Â Â Â Â Â Â string destination =Â Â Â Â Â Â Â Â Â Â Â Â Environment.CurrentDirectory +Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â "\\backups\\" + backupname; Â Â Â Â Â Â Â Â p.StartInfo.Arguments = Â Â Â Â Â Â Â Â Â Â Â Â " /c copy " + source + " " + destination; Â Â Â Â Â Â Â Â p.StartInfo.FileName = "cmd"; Â Â Â Â Â Â Â Â p.StartInfo.CreateNoWindow = true; ...code removed for brevity
- To remediate the command injection vulnerability, add a new method that utilizes the built-in file copying function:
public async Task FileCopyAsync(string sourceFileName,    string destinationFileName,     int bufferSize = 0x1000,     CancellationToken cancellationToken =         default(CancellationToken)) {     using (var sourceFile =         File.OpenRead(sourceFileName))     {         using (var destinationFile =            File.OpenWrite(destinationFileName))         {             await             sourceFile.CopyToAsync(destinationFile,                bufferSize, cancellationToken);         }     } }     Â
- Rewrite the entire body of the
BackupDB
method and use the newly created method:public async Task BackupDB(string backupname) { Â Â Â Â string source = Â Â Â Â Â Â Â Â Environment.CurrentDirectory + Â Â Â Â Â Â Â Â Â Â Â Â "\\OnlineBank.db"; Â Â Â Â string destination = Â Â Â Â Â Â Â Â Environment.CurrentDirectory + "\\backups\\" Â Â Â Â Â Â Â Â Â Â Â Â + backupname; Â Â Â Â await FileCopyAsync(source, destination); }
We have refactored the BackUpDB
method to use the FileCopyAsync
method to limit your code to just perform file copying tasks, thereby preventing the execution of unwanted shell commands.
How it works…
In our sample solution, administrators are allowed to provide a name to create a database backup. The BackUpDB
method accepts a user-controlled input parameter of the string
type. The input string is used to form a command that will initiate a command shell to have files copied from the source to the destination.
The added input string is expected to have the destination filename, but this can be manipulated to include commands that are more than just a value for an argument. Without validation or sanitization, this could cause the application to execute unwanted shell commands under the web application's identity and authorization.
There's more…
One option of stopping OS command injection is to implement proper validation through the whitelisting technique. This technique can be achieved by using regular expressions (see the Input validation recipe in Chapter 1, Secure Coding Fundamentals):
- Add a reference to the
System.Text.RegularExpressions
namespace:using System.Text.RegularExpressions;
- Then, use the
RegEx
class and itsIsMatch
method to validate the input against a pattern to only accept valid characters:public async Task BackupDB(string backupname) { Â Â Â Â var regex = new Regex(@"^[a-zA-Z0-9]+$"); Â Â Â Â if (!regex.IsMatch(backupname)) return; Â Â Â Â using (Process p = new Process()) Â Â Â Â { Â Â Â Â Â Â Â Â string source = Â Â Â Â Â Â Â Â Â Â Â Â Environment.CurrentDirectory +Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â "\\OnlineBank.db"; Â Â Â Â Â Â Â Â string destination = Â Â Â Â Â Â Â Â Â Â Â Â Environment.CurrentDirectory +Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â "\\backups\\" + backupname; Â Â Â Â Â Â Â Â p.StartInfo.Arguments = " /c copy " + source + Â Â Â Â Â Â Â Â Â Â Â Â " " + destination; Â Â Â Â Â Â Â Â p.StartInfo.FileName = "cmd"; Â Â Â Â Â Â Â Â p.StartInfo.CreateNoWindow = true; // code removed for brevity
We have now added a whitelisting validation with the use of the IsMatch
method. The IsMatch
method prevents non-alphanumeric characters and input from being processed in the succeeding lines of code, mitigating the risk of command injection.