http://www.developer.com/

Back to article

10 Simple Security Tasks for Locking Down Your LAMP Website


May 26, 2010

In their rush to build great website features, many developers overlook security issues -- even though a number of simple tasks can collectively reduce the possibility of a successful outside attack. In this article, I'll highlight 10 powerful security procedures for websites running on the LAMP (Linux, Apache, MySQL, and PHP) stack. These tasks can be implemented so easily that you'll wonder why you didn't use them months ago.

1. Filter All Input

If there were ever an official logo for the world's Web developers, the tagline would surely read "Filter All Input." Neglecting to validate user input properly has allowed the overwhelming majority of successful website attacks since the Web's earliest days, despite being a trivial part of the development process. But before describing the remedy, let's review a typical attack involving a website that has glossed over the filter process.

Suppose you create an online tool that converts currency rates by allowing the user to choose a currency, monetary sum, and a target conversion currency. Given these values, the site talks to a legacy script running on the server to perform the conversion and display the results to the user. Imagine you were in a hurry when creating this feature and decided to forgo filtering user input. An attacker takes advantage of the oversight and enters the following value, resulting in your entire website being deleted:

100; rm -rf 


Although there are a number of ways you can filter user input to ensure that appropriate values are being entered, perhaps the easiest solution is to use PHP's native filtering functions, which are available as of PHP 5.2.0. For instance, you easily could use the following function to filter the monetary value and ensure that only integers are provided:

$money = filter_var($_POST['money'], FILTER_SANITIZE_NUMBER_INT); 


2. Escape All Output

Most developers learn early on in their careers the importance of filtering user input, yet somehow manage to overlook the equally important task of escaping all output. Suppose that you spent a good deal of time refactoring your site to filter all user input, yet managed to miss a form that allows users to insert a new forum message. An attacker could take advantage of the oversight by inserting the following "message" and adding it to the forum:

<script>document.location="http://www.example.com"</script> 


The next person who visits the page containing the attacker's message immediately will be transported to www.example.com. This website could be a competing forum, or even a site that is designed to look exactly like yours! Avoiding such issues is incredibly simple: just use PHP's htmlentities() function to escape all output that was originally created by a third-party:

echo htmlentities($message); 


Doing so will cause all suspect characters such as > to be converted to their HTML entity equivalent, resulting in the attacker's message being converted to the following string:

<script>document.location='http://www.example.com'</script> 


3. Use Prepared Statements

Returning to the theme of filtering all user input, one of the most common security exploits is an attack known as SQL injection. SQL injection works by sending data to the database that causes a misinterpretation of the developer's intent and results in the application executing dangerous SQL code. For instance, suppose you provide a form that prompts the user to subscribe to your newsletter by submitting his e-mail address. The INSERT query used to insert the e-mail address into your database looks like this:

$db = new mysqli("localhost", "webuser", "secret", "newsletter");
$email = $_POST['email']; $query = "INSERT into subscribers SET email = '$email'";
if ($db->query($query)) { echo "Thank you for subscribing!"; }


While this snippet may look harmless, it is actually extremely dangerous! Suppose an attacker came across this page and happened to submit the following value as his e-mail address:

blah@example.com'; DROP TABLE subscribers --; 


Doing so would result in the following query being executed:

INSERT into subscribers SET email = 'blah@example.com'; DROP TABLE subscribers --' 


I doubt this is the type of subscriber you were hoping for! To eliminate the possibility of such attacks, you should instead use prepared statements to interact with the database:

$db = new mysqli("localhost", "webuser", "secret", "newsletter");
$email = $_POST['email']; $stmt = $db->prepare("INSERT into subscribers SET email = ?");
$stmt->bind_param('s', $email); if ($stmt->execute()) { echo "Thank you for subscribing!"; }


Due to space limitations I won't go into the details regarding prepared statement syntax. For more information, read this excellent Zend DevZone article, which devotes a section to the MySQLi extension's prepared statement syntax.

4. Disable Error Reporting

PHP's default error-reporting configuration will result in all errors except for notices being reported. Those errors will be displayed to the browser. While you will almost certainly want to leave this behavior untouched during the development process, you should make sure that the display_errors directive is disabled in your production environment. Neglecting to do so could provide an attacker with clues about how to exploit your site using its faulty code. To disable this directive, open your php.ini file and set the display_errors directive like so:

display_errors 0 


If you aren't able to modify the php.ini file in your production environment, you can set this directive within an .htaccess file using this syntax:

php_value display_errors 0 


5. Audit Your httpd.conf File

Apache's default configuration likely has many more options enabled than you need to operate your website, which opens up the possibility for security issues to crop up in areas you're likely not monitoring or don't even know are enabled. For instance, a default installation of Apache on my Ubuntu laptop indicates that 13 modules are enabled, including modules such as Status and CGI. If you're not using features like these, you should disable them.

However, this is only one example of many Apache features that should be disabled. For instance, Apache's default configuration displays information about the server version, operating system, and port at the bottom of pages in which it reports an error such as a 404 or when listing directory contents. You can test this behavior by attempting to access a non-existent page on your Web server. If this behavior hasn't been disabled, you'll see something like this below the error message:

Apache/2.2.12 (Ubuntu) Server at localhost Port 80 


Disable this behavior by opening httpd.conf, locating the ServerSignature and ServerTokens directives, and setting them like so:

ServerSignature Off ServerTokens Prod


6. Upgrade Early and Often

Despite being actively developed for almost 15 years, PHP is still subject to the occasional serious security issue. Over the years the alarm bells, which used to ring with security-related announcements, seem to have waned (not due to any fault on the PHP developer's part but rather I suspect due to the fact that the Web is a much busier place than in years past). For instance, although PHP 5.3 has been out for several months now, chances are quite a few developers are still running a PHP 5.2.X release, which up until 5.2.13 happens to contain several significant security issues.

Given the deluge of other information you're regularly perusing online, hoping a relevant feed reader headline catches your eye probably isn't the best way to keep abreast of required security updates. Instead, you should proactively monitor for any security-related news pertinent to your software stack; the easiest way to do so is by subscribing to the various project's announcement mailing lists. These low-volume lists will announce significant releases, often with links to the change logs (PHP announce list , Apache announce list , MySQL announce list).

7. Restrict the MySQL User Privileges

MySQL offers a powerful user management feature, which not only makes it easy to create and delete users, but also to selectively assign privileges that determine how the user can interact with your databases at the database, table, or even column level. For instance, you can restrict a user to being able to insert and update rows only into a particular table of a given database. Think back to the earlier SQL injection example involving an attacker successfully executing a DROP TABLE command. Even if your site wasn't properly filtering user input, the attack would fail if you configured the database user responsible for running the website so that it was unable to execute the DROP TABLE command.

Given the goal of hampering attackers by introducing several levels of security into your website's operation, consider configuring your website's database user privileges to only those that are necessary to power the site. You can learn more about MySQL's access privilege system here.

8. Disable your phpinfo Script

Executing the phpinfo() function within a PHP script will produce a long list of PHP- and server-specific configuration data, including information about the PHP version, extensions configured, and configuration directive settings. This information can be useful during the development process, providing an easy way to diagnose issues involving settings such as the include_path directive and sessions.

This information could be equally beneficial to an attacker searching for ways to exploit your system, so be sure to remove this script when deploying the site. If you prefer to continue referencing it from the production server, configure Apache to restrict the file's access to your IP address.

9. Disable Potentially Dangerous PHP Functions

Continuing with the theme of layered security, consider adding potentially dangerous functions such as exec(), system(), and phpinfo() to the disable_functions directive within your php.ini file.

10. Mind the Document Root

Remember that unless your server is specifically configured to restrict access to certain files or directories, anything placed within your website's document root is subject to access via the browser! Therefore, be sure to remove image templates, development notes, documentation, and anything else that could provide an attacker with useful fodder for further exploitation.

Conclusion

The 10 tasks discussed in this article are all easily completed, and can greatly reduce the possibility of downtime due to an attack. What else are you doing to protect your site? Tell us about it in the comments!

About the Author

Jason Gilmore is the founder of EasyPHPWebsites.com. He also is the author of several popular books, including "Easy PHP Websites with the Zend Framework," "Easy PayPal with PHP," and "Beginning PHP and MySQL, Third Edition."

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date