Why is this an issue?
OS command injections occur when applications build command lines from untrusted data before executing them with a system shell.
In that case,
an attacker can tamper with the command line construction and force the execution of unexpected commands. This can lead to the compromise of the
underlying operating system.
What is the potential impact?
An attacker exploiting an OS command injection vulnerability will be able to execute arbitrary commands on the underlying operating system.
The impact depends on the access control measures taken on the target system OS. In the worst-case scenario, the process runs with root privileges,
and therefore any OS commands or programs may be affected.
Below are some real-world scenarios that illustrate some impacts of an attacker exploiting the vulnerability.
Denial of service and data leaks
In this scenario, the attack aims to disrupt the organization’s activities and profit from data leaks.
An attacker could, for example:
- download the internal server’s data, most likely to sell it
- modify data, send malware
- stop services or exhaust resources (with fork bombs for example)
This threat is particularly insidious if the attacked organization does not maintain a disaster recovery plan (DRP).
Root privilege escalation and pivot
In this scenario, the attacker can do everything described in the previous section. The difference is that the attacker also manages to elevate
their privileges to an administrative level and attacks other servers.
Here, the impact depends on how much the target company focuses on its Defense In Depth. For example, the entire infrastructure can be compromised
by a combination of OS injections and misconfiguration of:
- Docker or Kubernetes clusters
- cloud services
- network firewalls and routing
- OS access control
How to fix it in .NET
Code examples
The following code is vulnerable to command injections because it is using untrusted inputs to set up a new process. Therefore an attacker can
execute an arbitrary program that is installed on the system.
Noncompliant code example
public class ExampleController : Controller
{
public void Run(string binary)
{
Process p = new Process();
p.StartInfo.FileName = binary;
p.Start();
}
}
Compliant solution
public class ExampleController : Controller
{
public void Run(string binary)
{
if (binary.Equals("/usr/bin/ls") || binary.Equals("/usr/bin/cat"))
{
// only ls and cat commands are authorized
Process p = new Process();
p.StartInfo.FileName = binary;
p.Start();
}
}
}
How does this work?
Allowing users to execute operating system commands generally creates more problems than it solves.
Anything that can be done via operating system commands can usually be done via a language’s native SDK.
Therefore, our first suggestion is to
avoid using OS commands in the first place.
However, if the application requires running OS commands with user-controlled data, here are some
security suggestions.
Pre-Approved commands
If the application aims to execute only a small number of OS commands (for example, ls
, pwd
, and grep
), the
cleanest way to avoid this problem is to validate the input before using it in an OS command.
Create a list of authorized and secure commands that you want the application to be able to execute. Use absolute paths to avoid any ambiguity.
If a user input does not match an entry in this list, it should be rejected because it is considered unsafe.
Depending on the number of commands you want the application to support, the list can be either a regex string or any array type. If you use
regexes, choose simple regexes to avoid ReDOS attacks. For example, you can accept only a specific set of executables, by using
^/bin/(ls|pwd|grep)$
.
Important note: The application must do validation on the server side. Not on client-side front-ends.
Neutralize special characters
If the application is to execute complex commands that cannot be controlled thanks to pre-approved lists, the cleanest approach is to use special
sanitization components, such as System.Diagnostics.ProcessStartInfo
.
The library helps you to get rid of common dangerous characters, such as:
If user input is to be included in the arguments of a command, the application must ensure that dangerous options or argument delimiters are
neutralized.
Argument delimiters count '
, -
and spaces.
For example, the find
command from UNIX supports the dangerous argument -exec
.
In this case, option processing can be
terminated with a string containing --
or with special options. For example, git
supports --end-of-options
since its version 2.24.
Here, using the ProcessStartInfo
structure helps escaping the passed arguments and internally creates a single string given to the
operating system when System.Diagnostics.Process.Start()
is called.
Resources
Documentation
Standards