Query and command injections are some of the most devastating classes of vulnerabilities in existence. This series of blog posts will teach you how to identify and prevent this vulnerability from occurring.
What is command injection?
Similar to SQL injection, the root cause of command injection is unvalidated data. Command injection can occur when unsanitized, user-controlled input is passed as input to execution calls. If a developer is not careful, dynamically built commands can be used by an attacker to perform arbitrary execution on the underlying operating system. This is accomplished by appending additional commands to the intended command string.
As an example, let’s examine a small snippet of vulnerable PHP code that was identified during an actual assessment. To protect the innocent and ourselves the code has been anonymized.
Vulnerable PHP code
function getReport($jsonArgs){$cmd = '/usr/local/bin/report.sh -a ' . $jsonArgs ->reportId;output = shell_exec($cmd);...}
Tracing back through the code, and as you can probably already guess, the jsonArgs
object is passed from an untrusted source that can be influenced by the user. Since no attempt has been made to sanitize the serialized data by the time getReport()
is called, an attacker can control the argument of shell_exec()
by manipulating the json->reportId
value. As a simple demonstration, consider the following request:
HTTP Request
X-REquested-With: XMLHttpRequestReferer: http://www.domain.comContent-Length: 159PHPSESSID:6a33e696d72fd447caa0df9e229aad3Pragma: no-cacheCache-Control: no-cache{"action":"acmeDirect","method":"getReport","data":["397MPIS10COSHTUBJGBG4B8365G9GJ9475Y4F21L",{"reportId":"test; sleep 25; echo"}],"type":"rpc","tid":239}
When the application executes the command via a shell, it will also run the appended commands injected via the input parameter. While this example is benign and will simply cause the request to wait 25 seconds before returning, much more sinister requests can be injected.
Example command executes via a shell
/usr/local/bin/stats.sh -a test; sleep 25; echo
Preventing Command Injection
Similar to SQL injection, the starting point begins with validating user input. In practice, input should be validated as close to the external interface as possible, but for simplicity’s sake, we show our validation process within the getReport()
function. Implementation details will vary depending on the language. Regarding the particulars of this PHP example, an easy fix would first ensure reportId
is an integer and only an integer via the is_numeric()
function. We can then use escapeshellarg()
function to wrap the input to ensure its passed as a single string to the command.
Easy Fix Example PHP
function getReport($jsonArgs) {if (is_numeric($jsonArgs ->reportId) && $jsonArgs ->reportId >= 1) {$cmd = '/usr/local/bin/report.sh -a ' . escapeshellarg($jsonArgs ->reportId);output = shell_exec($cmd);} else {// error}...}
But wait… Consider this
While the simple fix is secure, it doesn’t consider underlying vulnerabilities that may exist within the PHP language itself. A more secure solution would be to avoid the use of a shell interpreter within PHP by using pcntl_fork
and pcntl_exec
, but many developers would tell you that those functions are not easy to work with. That is why frameworks that default to the more secure design pattern of avoiding the shell interpreter all together are recommended.
Python is one such language where developers are directed towards using modules that will not invoke the shell unless forced too. This creates a “secure by default” environment where the entire class of vulnerabilities may not exist unless the developer goes out of their way to use a less secure design.