Thursday, July 28, 2011

Security precautions before you start using PHP on your Apache server, step by step

SkyHi @ Thursday, July 28, 2011

Introduction - what is PHP?

PHP is a server side scripting language. You can embed PHP code in your web pages along with HTML. When your server receives a request for a page, it first gives the page to the PHP handler program. The PHP handler outputs HTML code as-is, but when it encounters PHP commands, it executes them. Any HTML generated by the PHP commands is also output. The end result is a web page with content that has been customized on the server before being sent to whoever requested it.
PHP has capabilities that make it a potential security risk:
  • It can receive and process data from the "outside world"
  • It can be programmed to actively fetch data from anywhere on the internet
  • It is able to read and write files on the server
If you have a website, you can expect to be under constant attack from robots attempting to a) "send in" malicious data and scripts from the outside world, b) trick your server into fetching malicious scripts and running them, c) read and write files on your server. Their goal is to take control of your site and use it for their own purposes. 
This article gives configuration settings for PHP and rules for PHP coding that are effective at blocking the most common types of attacks. The configuration lines are few and the rules are simple.
There are two files where PHP configuration commands can go: php.ini or Apache .htaccess.
You should use php.ini as your first choice, if you can. You should be able to use it if:
  • You are on shared hosting at a webhost that uses suPHP or any other configuration that allows individual users to create their own php.ini files. If in doubt, ask them.
  • You are on dedicated hosting (your own rented server), or
  • You host your own website on your own server.

1a) Configure PHP settings with a php.ini file

php.ini specifies the configuration settings PHP will use when it is running on your website. It determines what things PHP scripts are allowed to do and what they are prohibited from doing. The following settings affect security.
In your public_html (the same folder where your site's main home page is), create a text file called php.ini that contains these lines. Instructions for customizing the text shown in red are farther down this page:
allow_url_fopen = Off
display_errors = Off
display_startup_errors = Off
log_errors = On
error_reporting = E_ALL
error_log = /home/yourUserID/public_html/phperr.txt
expose_php = Off
magic_quotes_gpc = On
magic_quotes_sybase = Off
register_globals = Off

Explanations:

These explanations are brief. This page of the PHP Manual has links to more.

allow_url_fopen = Off

allow_url_fopen = Off is especially important. It prevents URLs (internet addresses) from being used in PHP include() statements and in some other places. A command such as include("http://website.com/page.php") will not be allowed to execute. Only files that reside within your website can be included, and you must refer to them by their filepath names, not by their internet URLs. You won't be able to include a file from a different server, but neither will anybody else. When someone else does it maliciously by embedding the URL in an otherwise innocent-looking HTTP request and hoping that your script can be tricked into including and running their script, it's called a Remote File Inclusion (RFI) attack. Having allow_url_fopen = Off dooms all such attacks to fail.
Some webmasters think they need to have allow_url_fopen = On because their pages are already coded to use URLs to include files from their own site or from some external site. It is worth expending some effort to try to stop doing that so that you can turn allow_url_fopen off:
  1. You can include a file from your own site simply by specifying its path and filename. Here is an example how to convert a URL include to one that does not use a URL:

    Assume your current code looks like this:

    include('http://yoursite.com/page.php');

    You would convert it to this:

    include($_SERVER['DOCUMENT_ROOT'] . '/page.php');

    $_SERVER['DOCUMENT_ROOT'] is a superglobal variable calculated by the server to be the root folder of your site, the equivalent of "/", which is usually public_html. Note that it does not provide a trailing "/", so you must provide a leading "/" in '/page.php'. Now you have a reliable method to refer to any file without having to use relative paths and without using a URL unnecessarily.
     
  2. If you include static content (that doesn't change) from another of your websites, such as
    include('http://myothersite.com/includes/footer.php'),
    you can make a copy of that content in the current site and then include it locally as described above. Having duplicate copies of a few files is a small price to pay for the better security of having allow_url_fopen Off.
     
  3. If you cannot avoid it and must include content from a remote site using URLs, you'll need to set allow_url_fopen = On. You can still get some protection from RFI attacks by using .htaccess to ban incoming requests that contain potentially malicious URLs. See Section 1b) below and follow the link there.
     
  4. You can also prevent RFI, without needing special configuration settings, by writing your PHP scripts to carefully check incoming data to make sure it is not malicious. How to do that is described here

display_errors = Off
display_startup_errors = Off
log_errors = On
error_reporting = E_ALL

These specify that all errors and warnings will be logged to your error log text file. NO errors or warnings will be displayed on any web page that is sent out from your server. Errors should never be displayed publicly because they can help someone figure out how to attack your server. Remember to check your error log when you are testing new code. 

error_log = /home/yourUserID/public_html/phperr.txt

This defines the path and file to which your PHP errors and warnings are logged. Change yourUserID to the cPanel or other UserID assigned to you by your webhost. The filename can be anything you want. The path starting with /home seems to be a common one on Linux servers, but it might vary depending on your webhost. If the above doesn't work, ask them what it should be. public_html is only shown above as an example of where it fits in the path. You don't have to put your error log inside public_html. See below.
I recommend using a text file for error logging (as shown above), and not using the "system log" option that you might see mentioned. Your text file will accumulate errors indefinitely until you empty it, while the Apache system log can be flushed unpredictably.
Your error log file should be in an area of your webspace that is not publicly accessible. Any one of these methods will protect it:
  1. Put it in a folder that is not inside /public_html.
     
  2. Protect it with an .htaccess file that prohibits web access, like this:

    In cPanel > File Manager, create the folder.
    Inside the folder, create a file called .htaccess (with the leading period).
    Put this text in the file:

    order allow,deny
    deny from all

    Save the file.
    Test it by trying to go to http://yoursite.com/foldername/
    You should get a 403 Forbidden error page.
     
  3. Put it in a folder that is inside /public_html, but apply password protection to the folder. You can do this in cPanel.
With A) and B), you can only view the file with cPanel > File Manager or FTP, not by browser. With C), you can view the file in your browser by entering the password.

expose_php = Off

Not particularly important, but it doesn't hurt. The headers that accompany outgoing pages will not reveal that PHP is running or its version.

magic_quotes_gpc = On

The PHP manual recommends setting this to Off, and dealing with quotes in a secure manner on your own, but we're assuming you don't know how to do that yet, and that you also don't actually have any need yet for the situations it addresses, so for now the best setting is On.

magic_quotes_sybase = Off

Another special setting of "magic quotes". This should be Off.

register_globals = Off

register_globals = Off is especially important. You've probably seen URLs that look like this: http://site.com/index.php?something=somevalue. When register_globals is On, the variable called something is passed into your script with its value set to somevalue. When register_globals is Off, variables passed in like this are not automatically dumped into your script's variable list. This makes it harder for someone to inject their own code.

safe_mode = Off

This setting is not in the "recommended php.ini" above. I only mention it because you might run across it and wonder what it is and how it should be set. It restricts the permissions with which PHP scripts run. However, some very popular third party scripts, which you might want to use eventually, will not run properly when it is set to On. In addition, if your webhost uses suPHP, safe_mode serves no purpose. Lastly, beginning with PHP 6, safe_mode doesn't even exist. Therefore, it is best left out of your php.ini file, or, if present, set to Off.

1b) Alternative: configure PHP in .htaccess

If your webhost does not allow you to create your own php.ini, you can put configuration commands in .htaccess instead. Unfortunately, not all php.ini commands have .htaccess equivalents, but some of them do.
Put the following lines in a part of your public_html/.htaccess file that is not delimited by HTML-style tags such as the tags in the example in Section 3.1 below. The following lines have the same effects as their php.ini counterparts described in Section 1a) above, but notice that the format of the commands is different:
php_flag display_errors Off
php_flag display_startup_errors Off
php_flag log_errors On
php_flag magic_quotes_sybase Off
php_flag magic_quotes_gpc On
php_flag register_globals Off
php_value error_log /home/yourUserID/public_html/phperr.txt
php_value error_reporting 2147483647

The most important is the register_globals line.
It is very unfortunate that allow_url_fopen = Off has no .htaccess counterpart (although it will in PHP6). Because you cannot stop PHP from reading a file from a remote server, you need to make sure requests that try to do that maliciously are blocked so they cannot reach the PHP processor. A previous article has instructions how to use .htaccess to block requests that might be Remote File Inclusion attacks.
The following two also cannot be set in .htaccess, but they are unimportant:
expose_php = Off
safe_mode = Off

1c) What if the above configuration settings don't work?

If you ever write code that won't run properly with the above settings, you are leaving behind your "beginner" status and need to study PHP security more carefully before you go any further. The Security section of the PHP Manual is one reference that should be read at some point, but it isn't easy and probably isn't the best place to start. 

2) Viewing your PHP settings

Your server can give you a complete report of all your PHP settings.
  1. Create a text file with a .php extension, containing just this line:


     
  2. Upload it to your server into (preferably) a password protected folder.
  3. Open your browser and type the path into the address bar:
    http://yoursite.com/whatever/filename.php.
  4. Enter your username and password to enter the protected folder and view the result page.
  5. Save or print the result page to your local computer for reference.
  6. Delete the .php file from your server. It is not good to leave this file lying around where somebody else can run it and see how your server is configured. 

3) Modify your .htaccess file

  1. Important if you use php.ini: In your public_html/.htaccess file, add the following lines if they are not already there. Reference information for these lines is in the Apache manual section for mod_access:
# This denies all web access to your php.ini file.

order allow,deny
deny from all

  1. Required when using suPHP: If your webhost uses suPHP, you will need to enter a suPHP_ConfigPath line that tells PHP the location of your php.ini file. It will look something like this, but if this doesn't work, ask your web host what the line should be. Put this in a part of the file that is not between HTML-style tags like the tags in the paragraph above:

    suPHP_ConfigPath /home/yourUserID/public_html
     
  2. Optional: If you are adding PHP code to your existing .html files and don't want to rename them all to .php, you can instruct the server to send all .html files through the PHP processor as if they had .php extensions. To do that, add one of these lines into .htaccess, in a part of the file that is not delimited by tags. If one doesn't work, try another; experiment. 

    If you are using suPHP:
    AddHandler x-httpd-php .htm .html

    If you are not using suPHP:
    AddType application/x-httpd-php .htm .html

    Apache 2 without suPHP:
    AddHandler application/x-httpd-php .htm .html

4) One more php.ini setting, advanced...

The settings list in Section 1a) was supposed to be as simple as possible, usable by anyone, with a minimum of effort required, but there is one more php.ini setting that's worth using if you can. Here is an example of its use, with a list of some of the functions that could be disabled for increased security:
disable_functions = exec,shell_exec,passthru,system,eval,show_source,proc_open, popen,parse_ini_file,dl,(comma-separated list of function names)
This tells PHP not to allow the listed functions to be executed by any script in your site. The functions listed above are especially powerful, and many malicious scripts use them. By blocking their use, you block the scripts from causing much of their damage even if they do somehow manage to get into your site and run.
However, some of these functions are used by popular third party PHP scripts such as forums, blogs, galleries, and shopping carts, so the reason I call this an "advanced" setting is that before using this line you must search all the PHP code in your site to make sure you don't disable functions that your site requires. Nevertheless, disabling functions you don't use is worthwhile if you don't mind doing the research.
disable_functions is for php.ini only. It has no .htaccess equivalent.

5) Memorize these best coding practices

Beginner Rules:

  • Never use the PHP eval() function.
  • Never use PHP to connect to a database. (What this means is that while you are a beginner, you should not connect to a database at all, until you have studied the hazards of "SQL injection" attacks and understand how to write your code properly to guard against them.)
  • Never use the $_GET, $_POST, $_COOKIE, $_REQUEST, or $_FILES variables. These bring data into your script from the outside world. The data has the potential of being malicious.

Advanced Rule:

When the time comes that you need to break one of the Beginner Rules, first do a web search on: PHP security and spend a few days practicing proper coding techniques for the methods and functions you plan to use. 

6) Things to do

If necessary, email your web host. Ask these questions, and then modify your php.ini or .htaccess file as needed:
  1. Am I allowed to have my own php.ini file?
     
  2. I want to create an error log for PHP. Are these paths ok?

    php.ini  : error_log = /home/yourUserID/public_html/phperr.txt
    .htaccess: php_value error_log /home/yourUserID/public_html/phperr.txt

     
  3. Does my server use suPHP? If so, is this the correct line for .htaccess?

    suPHP_ConfigPath /home/yourUserID/public_html

7) You're ready to start. Good luck and have fun.

Additional security precautions for protecting your website are at
How to prevent your website from being hacked. How to repair a damaged site.

REFERENCES
http://25yearsofprogramming.com/blog/20070808.htm