Login

Securing Your PHP Website

In this second part of a three-part series on secure PHP programming, you’ll learn how to hide the fact that you’re using PHP to drive your site, how to hide sensitive data, and more. This article is excerpted from chapter 21 of the book Beginning PHP and Oracle: From Novice to Professional, written by W. Jason Gilmore and Bob Bryla (Apress; ISBN: 1590597702).

Hiding PHP

You can also hide, or at least obscure, the fact that you’re using PHP to drive your site. Use the expose_php directive to prevent PHP version details from being appended to your Web server signature. Block access to phpinfo() to prevent attackers from learning your software version numbers and other key bits of information. Change document extensions to make it less obvious that pages map to PHP scripts.

expose_php = On | Off

Scope: PHP_INI_SYSTEM ; Default value: On

When enabled, the PHP directive expose_php appends its details to the server signature. For example, if ServerSignature is enabled and ServerTokens is set to Full , and this directive is enabled, the relevant component of the server signature would look like this:

The phpinfo() function offers a great tool for viewing a summary of PHP’s configuration on a given server. However, left unprotected on the server, the information it provides is a gold mine for attackers. For example, this function provides information pertinent to the operating system, the PHP and Web server versions, and the configuration flags, and a detailed report regarding all available extensions and their versions. Leaving this information accessible to an attacker will greatly increase the likelihood that a potential attack vector will be revealed and subsequently exploited.

Unfortunately, it appears that many developers are either unaware of or unconcerned with such disclosure because typing phpinfo.php into a search engine yields roughly 336,000 results, many of which point directly to a file executing the phpinfo() command, and therefore offering a bevy of information about the server. A quick refinement of the search criteria to include other key terms results in a subset of the initial results (old, vulnerable PHP versions) that would serve as prime candidates for attack because they use known insecure versions of PHP, Apache, IIS, and various supported extensions.

Allowing others to view the results from phpinfo() is essentially equivalent to providing the general public with a road map to many of your server’s technical characteristics and shortcomings. Don’t fall victim to an attack simply because you’re too lazy to remove or protect this file.

Change the Document Extension

PHP-enabled documents are often easily recognized by their unique extensions, of which the most common include .php , .php3 , and .phtml . Did you know that this can easily be changed to any other extension you wish, even .html , .asp , or .jsp ? Just change the line in your httpd.conf file that reads

AddType application/x-httpd-php .php

by adding whatever extension you please, for example

AddType application/x-httpd-php .asp

Of course, you’ll need to be sure that this does not cause a conflict with other installed server technologies.

{mospagebreak title=Hiding Sensitive Data}

Any document located in a Web server’s document tree and possessing adequate privilege is fair game for retrieval by any mechanism capable of executing the GET command, even if it isn’t linked from another Web page or doesn’t end with an extension recognized by the Web server. Not convinced? As an exercise, create a file and inside this file type my secret stuff. Save this file into your public HTML directory under the name of secrets with some really strange extension such as .zkgjg . Obviously, the server isn’t going to recognize this extension, but it’s going to attempt to serve up the data anyway. Now go to your browser and request that file, using the URL pointing to that file. Scary, isn’t it?

Of course, the user would need to know the name of the file he’s interested in retrieving. However, just like the presumption that a file containing the phpinfo() function will be named phpinfo.php , a bit of cunning and the ability to exploit deficiencies in the Web server configuration are all one really needs to have to find otherwise restricted files. Fortunately, there are two simple ways to definitively correct this problem, both of which are described in this section.

Hiding the Document Root

Inside Apache’s httpd.conf file, you’ll find a configuration directive named DocumentRoot. This is set to the path that you would like the server to consider to be the public HTML directory. If no other safeguards have been undertaken, any file found in this path and assigned adequate persmissions is capable of being served, even if the file does not have a recognized extension. However, it is not possible for a user to view a file that resides outside of this path. Therefore, consider placing your configuration files outside of the DocumentRoot path.

To retrieve these files, you can use include() to include those files into any PHP files. For example, assume that you set DocumentRoot like so:

Suppose you’re using a logging package that writes site access information to a series of text files. You certainly wouldn’t want anyone to view those files, so it would be a good idea to place them outside of the document root. Therefore, you could save them to some directory residing outside of the previous paths:

C:/Apache/sitelogs/ # Windows
/usr/local/sitelogs/ # Unix

Denying Access to Certain File Extensions

A second way to prevent users from viewing certain files is to deny access to certain extensions by configuring the httpd.conf file Files directive. Assume that you don’t want anyone to access files having the extension .inc . Place the following in your httpd.conf file:

<Files *.inc>Order allow,denyDeny from all
</Files>

After making this addition, restart the Apache server and you will find that access is denied to any user making a request to view a file with the extension .inc via the browser. However, you can still include these files in your scripts. Incidentally, if you search through the httpd.conf file, you will see that this is the same premise used to protect access to .htaccess .

{mospagebreak title=Sanitizing User Data}

Neglecting to review and sanitize user-provided data at every opportunity could provide attackers the opportunity to do massive internal damage to your application, data, and server, and even steal the identity of unsuspecting site users. This section shows you just how significant this danger is by demonstrating two attacks left open to Web sites whose developers have chosen to ignore this necessary safeguard. The first attack results in the deletion of valuable site files, and the second attack results in the hijacking of a random user’s identity through an attack technique known as cross-site scripting. This section concludes with an introduction to a few easy data validation solutions that will help remedy this important matter.

File Deletion

To illustrate just how ugly things could get if you neglect validation of user input, suppose that your application requires that user input be passed to some sort of legacy command-line application called inventorymgr that hasn’t yet been ported to PHP. Executing such an application by way of PHP requires use of a command execution function such as exec() or system() . The inventorymgr application accepts as input the SKU of a particular product and a recommendation for the number of products that should be reordered. For example, suppose the cherry cheesecake has been particularly popular lately, resulting in a rapid depletion of cherries. The pastry chef might use the application to order 50 more jars of cherries (SKU 50XCH67YU), resulting in the following call to inventorymgr :

Now suppose the pastry chef has become deranged from sniffing an overabundance of oven fumes and decides to attempt to destroy the Web site by passing the following string in as the recommended quantity to reorder:

50; rm -rf *

This results in the following command being executed in exec() :

exec("/opt/inventorymgr 50XCH67YU 50; rm -rf *");

The inventorymgr application would indeed execute as intended but would be immediately followed by an attempt to recursively delete every file residing in the directory where the executing PHP script resides.

Cross-Site Scripting

The previous scenario demonstrates just how easily valuable site files could be deleted should user data not be filtered. While it’s possible that damage from such an attack could be minimized by restoring a recent backup of the site and corresponding data, it would be considerably more difficult to recover from the damage resulting from the attack demonstrated in this section because it involves the betrayal of a site user that has otherwise placed his trust in the security of your Web site. Known as cross-site scripting, this attack involves the insertion of malicious code into a page frequented by other users (e.g., an online bulletin board). Merely visiting this page can result in the transmission of data to a third party’s site, which could allow the attacker to later return and impersonate the unwitting visitor. Let’s set up the environment parameters that welcome such an attack.

Suppose that an online clothing retailer offers registered customers the opportunity to discuss the latest fashion trends in an electronic forum. In the company’s haste to bring the custom-built forum online, it decided to forgo sanitization of user input, figuring it could take care of such matters at a later point in time. One unscrupulous customer decides to attempt to retrieve the session keys (stored in cookies) of other customers, which could subsequently be used to enter their accounts. Believe it or not, this is done with just a bit of HTML and JavaScript that can forward all forum visitors’ cookie data to a script residing on a third-party server. To see just how easy it is to retrieve cookie data, navigate to a popular Web site such as Yahoo! or Google and enter the following into the browser address bar:

javascript:void(alert(document.cookie))

You should see all of your cookie information for that site posted to a JavaScript alert window similar to that shown in Figure 21-1.

Figure 21-1.Displaying cookie information from a visit to http://www.news.com

Using JavaScript, the attacker can take advantage of unchecked input by embedding a similar command into a Web page and quietly redirecting the information to some script capable of storing it in a text file or a database. The attacker does exactly this, using the forum’s comment-posting tool to add the following string to the forum page:

// Return to original site
header("Location: http://www.example.com");
?>

Provided the e-commerce site isn’t comparing cookie information to a specific IP address, a safeguard that is all too uncommon, all the attacker has to do is assemble the cookie data into a format supported by her browser, and then return to the site from which the information was culled. Chances are she’s now masquerading as the innocent user, potentially making unauthorized purchases with her credit card, further defacing the forums, and even wreaking other havoc.

{mospagebreak title=Sanitizing User Input: The Solution}

Given the frightening effects that unchecked user input can have on a Web site and its users, one would think that carrying out the necessary safeguards must be a particularly complex task. After all, the problem is so prevalent within Web applications of all types, prevention must be quite difficult, right? Ironically, preventing these types of attacks is really a trivial affair, accomplished by first passing the input through one of several functions before performing any subsequent task with it. Four standard functions are conveniently available for doing so: escapeshellarg() , escapeshellcmd() , htmlentities() , and strip_tags() .

Note Keep in mind that the safeguards described in this section, and frankly throughout the chapter, while effective, offer only a few of the many possible solutions at your disposal. For instance, in addition to the four functions described in this section, you could also typecast incoming data to make sure it meets the requisite types as expected by the application. Therefore, although you should pay close attention to what’s discussed in this chapter, you should also be sure to read as many other security-minded resources as possible to obtain a comprehensive understanding of the topic.

Escaping Shell Arguments

The escapeshellarg() function delimits its arguments with single quotes and escapes quotes. Its prototype follows:

string escapeshellarg(string arguments)

The effect is such that when arguments is passed to a shell command, it will be considered a single argument. This is significant because it lessens the possibility that an attacker could masquerade additional commands as shell command arguments. Therefore, in the previously described file-deletion scenario, all of the user input would be enclosed in single quotes, like so:

/opt/inventorymgr ’50XCH67YU’ ’50; rm -rf *’

Attempting to execute this would mean 50; rm -rf * would be treated by inventorymgr as the requested inventory count. Presuming inventorymgr is validating this value to ensure that it’s an integer, the call will fail and no real harm will be done.

Escaping Shell Metacharacters

The escapeshellcmd() function operates under the same premise as escapeshellarg() , but it sanitizes potentially dangerous input program names rather than program arguments. Its prototype follows:

You should use escapeshellcmd() in any case where the user’s input might determine the name of a command to execute. For instance, suppose the inventory-management application is modified to allow the user to call one of two available programs, foodinventorymgr or supplyinventorymgr , by passing along the string food or supply , respectively, together with the SKU and requested amount. The exec() command might look like this:

exec("/opt/".$command."inventorymgr ".$sku." ".$inventory);

Assuming the user plays by the rules, the task will work just fine. However, consider what would happen if the user were to pass along the following as the value to $command :

blah; rm -rf *;
/opt/blah; rm -rf *; inventorymgr 50XCH67YU 50

This assumes the user also passes in 50XCH67YU and 50 as the SKU and inventory number, respectively. These values don’t matter anyway because the appropriate inventorymgr command will never be invoked since a bogus command was passed in to execute the nefarious rm command. However, if this material were to be filtered through escapeshellcmd() first, $command would look like this:

blah; rm -rf *;

This means exec() would attempt to execute the command /opt/blah rm -rf , which of course doesn’t exist.