31.5 LAMP Security Tips Around PHP

Linux, Apache, MySQL and PHP – altogether they mean LAMP – a solution stack of free, open source software. So, you see, I’m not talking about a lava lamp or a sun lamp or an ultraviolet one, either.

And if you desire a comprehensive, robust server, your IT infrastructure has to include all of these systems.

Monitis has put together a checklist of 101 actions you can take to maximize security around LAMP.  Hopefully we’re shedding a little light around this issue so that you can see some new ideas on how to make administering your system easier — so that, in turn, you can focus on more strategic tasks.

You can find previous posts about increasing security around Linux and Apache, as well as for MySQL. But in today’s post, we’ll offer tips on LAMP security around PHP, that is, a general-purpose server-side scripting language originally designed for web development to produce dynamic web pages.

Here goes; hope this helps:

1. Hide your PHP version number

As with hiding your Apache version number, your website’s visitors don’t need to know what version of PHP you’re running — so don’t tell them. Make sure that Apache doesn’t output your PHP version number, and make sure you don’t output it either.

2. Disable error display

By default, when PHP encounters an error it will output that error to the browser. This tells your visitors — and an attacker — sensitive information not only about how your source code is built up and what potential security holes it has, but also absolute paths to your code files that will later help an attacker to penetrate your system.

3. Use prepared statements

When you query a database — even if that database is not a MySQL database — you should always use a prepared statement if this is possible. Prepared statements prevent SQL injections at the root, and SQL injections are the most common vulnerability in web applications.

4. mysql_real_escape_string

Prepared statements are not always available, for example when you use an older version of PHP. If you cannot use a prepared statement for a database query, and the database you’re talking to is a MySQL database, then at least use mysql_real_escape_string to filter user inputs and reduce the chance of SQL injections.

5. White-list, don’t black-list

Sometimes, you need to use user input in a shell command or otherwise pass it to an external tool. The first thing you should try to do is simply not to do this. But of course, you might absolutely need to use that input in a shell command. In this case, use a white-list approach to filter the input, analogous to a default-deny policy.

6. Disable modules that aren’t used

You’ve already disabled the Apache modules that you aren’t using. The same goes for PHP modules: If you aren’t using it, disable it. If you find out later that you did need a specific module, you can always re-enable it. Undoing a break-in isn’t possible.

7. Enable safe_mode

PHP has excellent security features, but you have to enable them first. One of the main security features in PHP is safe_mode. My enabling safe_mode in your php.ini, you’re restricting all PHP scripts in what calls they can make and what files they can access. Note, however, that some PHP applications are not compatible with safe_mode. Your best bet is simply to try safe_mode and if it doesn’t work, either fix the application — possibly by enabling a simple compatibility setting — or disable safe_mode again.

8. Use open_basedir

“open_basedir” is a PHP setting that restricts PHP scripts to accessing certain files and folders within a specified directory. The point is that your PHP scripts probably don’t need access to the entire file system, but more likely only to web-related files (e.g. “/var/www”). Tell PHP to access only those files, and you’ve prevented accidental information leakage at the core.

9. Session naming

By default, PHP uses “PHPSESSID” for session cookies. This makes it easy for an attacker to see that you’re using PHP, even if you’ve told Apache not to talk about it. Make up your own name, maybe something generic like “SESSION”, and tell PHP to use that instead.

10. Application “readme” files

Remove all application readme files when they are no longer needed. Files such as “README”, “INSTALL.txt”, and so on are a great way for an attacker to tell what application and what version of that application you are using. This really helps them when they are looking for a security hole, as many older versions of web applications have well-known security holes than can be easily exploited. This point also applies to other application documentation, commonly found in the “doc”, “docs”, and “documentation” folder of the application.

11. Revision control info

Many application files contain a special version string inside them, usually of the form “$Id: filename version date time … $”. See how much information is in that simple string? Remove these strings to prevent an attacker from abusing them. Many Linux systems come with a special program called “ident” that will tell you if a file contains such a version string: Simply run “ident <filename>” and observe the output.

12. Custom favicon

Common software like Drupal and Plesk both include a nice-looking favicon that will be used if you don’t provide your own. With Drupal, this only applies inside the Drupal installation. With Plesk, however, this applies across the entire server. Make sure you supply your own favicon or remove the default one. Don’t make yourself an easy target: Hide your application info!

13. Disable register_globals

“register_globals” is a PHP setting that’s a bit of nostalgia from a different time. What register_globals does is to take every variable sent to a PHP script and make it a top-level variable in the script. For instance, visiting “/index.php?foo=bar” will do the equivalent of inserting “$foo = ‘bar’;” at the top of that script before running it. Use “$_POST” and family to access these variables instead, and disable register_globals.

14. Disable magic_quotes.

To prevent SQL injection, there are functions like mysql_real_escape_string. But back when SQL injections weren’t all that common, most developers would not care to escape user-supplied inputs before passing them to a command-line application or a database server. To prevent dangerous vulnerabilities, PHP includes a setting called “magic_quotes” that will automatically escape inputs. Escaping is a complicated issue, so magic_quotes does not actually work against most attacks. All it does is provide an inconvenience to developers and lures them into thinking their code is more secure than it really is. Disable magic_quotes and reject any code that relies on it being enabled.

15. The null character

If you’ve programmed in a system-level language before (think C), you will know of the null-terminated string idiom. Languages like PHP do not store strings in this null-terminated form, which means that PHP will sometimes interpret a string differently than the underlying system libraries. As with escaping, you need to be really careful when using user-supplied input and you should check for null (‘\0′) characters inside the string before passing it to another application.

16. Sanitize HTML

Cross-site scripting is a vulnerability where an attacker injects HTML code into your application that you didn’t ever want to display. This can be as simple as including a “<script>” tag in a field on your guestbook form. Make sure you escape such input and sanitize all HTML output. Even better, reject and report evil-looking inputs before they can get too far into your system.

17. Sanitize CSS

Dynamic CSS is an often over-looked issue in web application security. This can range from “style” attributes inside filtered HTML to whole stylesheets being included by an attacker. The problem with dynamic CSS is that it can be used to overlay elements that should not be overlayed and even change the meaning of forms entirely. As with HTML and any other dynamic input or output, you should sanitize all CSS.

18. White-list URLs

One common way of getting JavaScript code executed that you didn’t intend to execute is to include it as part of a URL. This can be done through a simple “javascript:” URL but could potentially be done with a “data” URL and others as well. Use a default-deny policy and while-list URLs that are allowed in your inputs. Common protocols you may want to allow are: http, https, ftp, ftps. Allow other protocols at your discretion.

19. Embedded Flash

YouTube videos are a very popular type of content to allow in your HTML. Make sure you sanitize any HTML code intended to include YouTube videos — or other Flash content — to make sure it does what you intended, not what an attacker intended. Arbitrary Flash content could be used for cross-site scripting, but also to exploit your visitors’ computers, so be careful.

20. Specify your encodings

The issue for file encoding is yet another issue where two systems may interpret content differently. An attacker can embed special codes in your HTML that cause tag starts (‘<‘) to be ignored by the browser if the browser has a different understanding of file encoding than your application does. You can imagine where this can lead up to, so specify the file encoding for all content you emit in the file (where applicable) and in the HTTP header.

21. Sanitize uploaded images

Some browsers are known to ignore the MIME type information you give to them, which an attacker can exploit by uploading an alleged image that actually contains script code which is then re-interpreted by the browser as a script. You could verify that the alleged image contains a proper image header, but this can be easily circumvented as well. Instead, consider re-encoding uploaded images before saving them and serving them to your users. PHP provides access to the excellent GD library for this.

22. Size of uploaded files

Check uploaded files to make sure they are within the limits you specified. Reject them otherwise. Also consider telling PHP to not accept file uploads beyond a certain size, so that an attacker is not able to fill up your entire file system and cause a “denial of service”.

23. Separate upload directories from script directories

Directories that store uploaded content should be walled off from script directories, otherwise an attacker may be able to upload a “.php” file and use it to attack your server. Block execution of any kind in the upload directories, and consider making the script directories read-only.

24. Serving uploaded files

Make sure you understand what mechanisms to use for serving uploaded files. If you can, simply redirect the user to the uploaded content. However, be aware that if the user can guess the name of other uploaded files, they will be able to bypass any security checks you otherwise have in place. If you cannot use redirection, make sure you use functions like readfile, not functions like include that could cause arbitrary script execution.

25. Check your paths

Make sure you serve only uploaded and permitted files. Watch out for specially crafted paths like “dir/subdir/../file.txt” — this example will access “dir/file.txt”. You cannot rely on simply replacing “..” in the path string, so try to use OS libraries to calculate the actual path, and then check that for correctness.

26. Use “Content-Disposition: attachment”

If you are serving files for download, use the “Content-Disposition: attachment” header. Otherwise an attacker may be able to run a cross-site scripting attack by uploading an evil HTML file.

27. Supply an accurate MIME type

When your application serves an image to the browser, make sure you specify this through the appropriate MIME type. Otherwise, the type of the file will be open to interpretation and an attacker may try to embed script code in a file that you intended to be an image.

28. Salt passwords

You should not be storing plain-text passwords in your database. However, a simple hash of the password is not a good idea either: It makes rainbow table attacks possible, which are attacks where an attacker pre-computes the hashes for many password combinations and then simply compares the hash stored in your database against their list of known hashes. Store a “salted hash” instead, using a HMAC or PBKDF2.

29. Cryptographic algorithms

It can be difficult to stay updated when it comes to cryptographic algorithms if your job is not in computer security. A basic tip: Don’t use MD5, which is now considered insecure. SHA-1 is considered secure but slowly loosing security as well, so consider a SHA-2 family algorithm for hashes. The same goes for DES vs. AES, RSA vs. EC-DSA, etc. Try to keep yourself updated, and when in doubt, use the more secure alternative.

30. Verify username/password as one item

Checking for a correct username before checking a correct password can give an attacker information about the list of users in your system. Don’t let them get this information. Note that this doesn’t just apply to the error message that you return. If you “error out” as soon as you know that the username is incorrect, the attacker may be able to use timing information to draw their own conclusion, independent of the error message.

31. session.use_only_cookies

As with making up your own name for the PHP session cookie name, tell PHP to use only cookies for its built-in sessions. It makes an attackers life more difficult and you shouldn’t be “low-hanging fruit” for anybody!

And finally, here’s tip #31.5: Monitor your site’s health with Monitis!