PHP Command Injection:
Examples and Prevention

stackhawk

StackHawk|April 30, 2021

Let’s see what command injection is, how it works in PHP, and understand how we can prevent command injection vulnerabilities.

There are many ways for a malicious user to take advantage of vulnerable websites. One of them is called command injection.

Today, you’ll learn what command injection is, how an attacker could use it to jeopardize your web application, and how you can prevent that from happening in your PHP applications.

Let’s get right to it!

A command injection attack is based on the execution of arbitrary (and most likely malicious) code on the target system.

What Is Command Injection?

A command injection attack is based on the execution of arbitrary (and most likely malicious) code on the target system.

In other words, it’s a way to use an application designed to do one thing for a completely different purpose.

Let’s take the example of a simple contact form. The purpose of such an application is simple: to allow people to leave their contact information for someone inside the company to get in touch. An attacker might want to use the application to steal information about other applicants, for example. And they might achieve that goal by injecting malicious code.

How Is Command Injection Performed?

In order for a command injection attack to occur, three things must happen at once:

  1. An application is using some function to make a call to a system shell.

  2. The application is passing unsafe/unvalidated data to such a call.

  3. An attacker is aware of this fact and acts on this knowledge.

Examples of Command Injection in PHP

These three PHP functions, if not used safely, can lead to the presence of this vulnerability:

The problem lies in the fact that all of them take an arbitrary string as their first parameter and simply forward it to the underlying operating system.

This doesn’t imply any risk if the string is written by the programmer (aka you), like this:

PHP
<?php

$command = "ls -l";

$output = exec($command);

But, since the $command variable is a string, nothing prevents you from writing something like the below:

PHP
<?php

$command = "ls ".$_GET['modifiers'];

$output = exec($command);

Which would, of course, produce the exact same result (since the value of $command would be “ls -l”).

So far, so good, right?

Now, let’s look at the following example:

PHP
<?php

$command = "ls ".$_GET['modifiers'];

$output = exec($command);

Here the command is being created from two sources: a fixed string (“ls “) and a URL parameter ($_GET['modifiers']).

This means that the actual command that’s about to be executed depends on user input.

Let’s say someone issues a request such as http://yourdomain.com?modifiers=-l.

When the code gets executed, the value of $_GET['modifiers] will be “-l”, which will result in the command “ls -l”.

No harm done, right?

But what if the user isn’t so nice?

What if they were to issue a request such as http://yourdomain.com?-l%3B+rm+%2A+-Rf?

The resulting command would be “ls -l; rm * -Rf”.

If you know a little bit about the Linux console, you should recognize the command “rm * -Rf” … and be very scared. (In case you’re not familiar with the Linux console, that command means “delete every file in this directory and its subdirectories.”)

It’s very much unlikely that someone will issue such a request by mistake.

But if someone wants to break your site and they know a little PHP (or any other programming language), it’s easy to create such a string.

In case you’re wondering where “-l%3B+rm+%2A+-Rf” comes from, it’s the result of the following:

urlencode("-l; rm * -Rf");

How Can an Attacker Craft a Command Injection Request?

Probably the easiest way is to use your own forms.

In the example above, imagine there’s a previous page that looks like this:

HTML
<html>
<form action="index.php">
   <input name="modifiers" type="text">
   <input value="Get file names" type="submit">
</form>
</html>

All the attacker would have to do is fill the field modifiers with “-l%3B+rm+%2A+-Rf” and hit “Get file names.”

Not too complicated, right?

If they wanted to go from the command line, they could use a simple tool such as cURL, like this:

curl http://yourdomain.com?modifiers=-l%3B+rm+%2A+-Rf

The advantage for the attacker of using this mechanism is that, by using this kind of tool, it’s really easy to automate the attack.

Where Does Unsafe Data Come From?

Unsafe data can come from several different sources. So far, we’ve discussed URL parameters, but an attacker can use any available way to transfer information to your application, such as the following:

  • POST
  • COOKIES

  • HTTP headers

  • Uploaded files

The POST example would be really similar to the GET one, so I’ll skip it to show you how this attack could be performed using HTTP headers.

It all begins with your code using such information in order to put together a command that will be issued to the operating system:

PHP
<?php 

$command = "mv ".$_SERVER['HTTP_X_FILE']." uploads/";
$output = system($command);

Now, all it takes for the attack to be successful is the addition of a carefully crafted request like this one:

curl http://yourdomain.com -H "X-file: a .; rm * -Rf //"

A very similar situation can be found if you have a script that looks somewhat like this:

PHP
# upload.php
<?php 

$uploaded_file = $_FILES['file_name']['tmp_name'];
$command = "cat ".file_get_contents($uploaded_file);
$output = system($command);
<!-- form.html -->
<!DOCTYPE html>
<html>
<body>

<form action="upload.php" method="post" enctype="multipart/form-data">
Select image to upload:
   <input type="file" name="fileToUpload" id="fileToUpload">
   <input type="submit" value="Upload file" name="submit">
</form>

</body>
</html>

And someone uploads a file containing something like this:

file.txt; rm * -Rf

You can likely see the pattern by now: blindly trusting user input is a bad idea.

So now that you know how an attacker might give you a hard time, let’s see what you can do to prevent it from happening.

How to Fix Command Injection Vulnerabilities

As you saw in the previous sections of this article, the problem comes from combining direct shell execution and using unsafe data.

So, in order to prevent this from happening, there are two strategies you might employ:

  1. Refrain from using direct shell execution functions.

  2. Avoid using unsafe data in combination with direct shell execution functions.

Don’t Use Direct Shell Execution Functions

Whenever possible, you should prefer to use built-in functions rather than OS commands.

For instance, if you need to delete files from the disk, you could use this:

exec("rm $file");

Or use this:

unlink($file);

By using the function unlink, you’re effectively eliminating the possibility of someone injecting malicious commands.

PHP has quite a lot of functions that you can use to mask operating system calls, such as the following:

  • rmdir for deleting a directory

  • chmod for manipulating file permissions

  • glob to iterate over the files in a given path

And a few more you can find here (just to name those that deal with the file system).

Another interesting side effect of using these functions is the increased portability of your code.

If you use the shell execution functions, the command is interpreted by the operating system itself instead of the PHP interpreter.

This means if you’re writing your code in a Windows environment but the production server is a Linux or similar, things aren’t going to work.

Of course, there will be cases where you need to run specific commands that aren’t part of the PHP core, like a particular binary program or a script you’ve created.

Let’s see how to deal with such situations.

Don’t Use Unsafe Data in Combination With Direct Shell Execution Functions

If you absolutely need to use system calls, then you must use proper validations on your data.

For instance, if you were going to issue a command such as this one:

PHP
<?php
    $targetIP = $_GET[ 'ip' ];
    $cmd = exec( "ping $target" );

You better make sure $targetIP is actually an IP address!

In order to perform such a validation, PHP provides a built-in function called filter_input, which you could use like this:

PHP
<?php
    if ($targetIP = filter_input(INPUT_GET,'ip',FILTER_VALIDATE_IP)) {  	$cmd = exec( "ping $target" ); } else { die("Please provide a valid IP address"); }

Similar functions exist to validate other sources of data (and also, there are several different filter types you can use to accommodate your particular needs).

You can manually scan through your code looking for dangerous code, or you can look into bringing on an automated tool that can be included right into your team’s workflow.

Add Automated Security Testing to Your Pipeline

Protect Your Code from Injections

Today you learned what a command injection attack is, what features a PHP application could have in order to be vulnerable to such an attack, and what to do to disable such a threat in your applications.

Now you have some homework to do!

You can manually scan through your code looking for dangerous code, or you can look into bringing on an automated tool that can be included right into your team’s workflow. One such tool is StackHawk, which can monitor every pull request and give you insights about how to solve them before they hit production.

You can read the details on how to implement it here.

This post was written by Mauro Chojrin. Mauro helps PHP developers hone their craft through his trainings, books, workshops, and other tools. He’s been in the IT industry since 1997 and has held roles such as developer, architect, and leader of technical teams. Mauro also likes to write and vlog.


StackHawk  |  April 30, 2021