Welcome to Dagon Design. In addition to free scripts, WordPress plugins, and articles, we offer a variety of services including custom theme design, plugin creation, and PHP scripting. Contact me for more information.

Updated Monday, October 17th, 2005 at 11:11am

Writing Secure PHP Scripts – Part 1

The most important element of any PHP script is security. In this article I will be discussing several methods of securing variables in PHP, with special regard to user input. While it is impossible to cover everything in a single article, my goal is to cover a range of topics, focusing on the most common security mistakes and how to fix them. This is aimed at those fairly new to PHP, but perhaps it will give others a few new ideas as well.

Securing your variables

In most versions of PHP, you can access the value of a variable before it is initialized. Consider this simple example:

if ($password == $the_password) {
    $logged_in = 1;
}
if ($logged_in == 1) {
    // secure stuff
}

All a visitor has to do is add ?logged_in=1 to the end of the URL and they will have access. While this may seem obvious, it is an extremely common problem with PHP scripts.

The best way to prevent this is to always make sure variables are declared before they are used. For this example, you can just add the following line at the top of the file:

$logged_in = 0;

Now the variable cannot be reset by a user since it is being declared before use.

Another recommendation is to enable error reporting. With the right setting, your scripts will generate an error if a variable is used before it is defined. While this might sound bothersome, it can be quite helpful for keeping things secure, since it will let you know of any variables you missed.

You can enable this for your entire server with a line in php.ini:

error_reporting = E_ALL

To enable this for a particular PHP script, just add this to the top of the file:

error_reporting(E_ALL);

If you do enable error reporting in your php.ini, but need it bypassed for a particular script, you can use this in your file:

error_reporting(0);

Variables passed to the script

On most servers, a visitor can put a variable in the URL which then gets turned into a variable in the script. There will be many times when you need information passed to the script in this manner, but only if you are asking for it. That is where the superglobals come in.

There are two superglobals which you will be using most of the time. $_GET and $_POST. $_GET is used to retrieve variables passed in the URL. $_POST is used to get values from html forms. In the past, you also had $HTTP_POST and $GET_VARS but they are depreciated and should not be used. Here is an example:

$the_name = $_GET['name'];

Keep in mind however that you need to make sure the value exists in the superglobal array before you use it, or you may receive an error. Try this:

if (isset($_GET['name'])) {
    $the_name = $_GET['name'];
} else {
    $the_name = "";
}

Now that you know how to get input from the user properly, there is still the matter of someone being able to pass random variables to your script. This is easy to fix with the register_globals option of PHP.

In your php.ini file, add this line:

register_globals = Off

Or in an .htaccess file:

php_flag register_globals off

Once again, if you need to re-enable register_globals for a particular script, you can do so with an alternate .htaccess file.

With register_globals disabled, the only way you will be able to accept user input is with the superglobals. Parameters passed to the script will no longer be automatically turned into variables.

Never trust the user

No matter what kind of input you are expecting, or what it will be used for, always validate it!

Even if you have a form textfield with a max length of 10 characters, you still need to make sure that the input is the proper size, because it is easy to bypass the very basic restrictions of html forms. It is even easy to send a multi-line input into a single-line textfield if you know what you are doing.

If you are displaying user input on the page, you must convert html characters to their browser-safe versions, so that the user does not cause code to execute on your server.

If you are putting user input into a MySql query you must use regular expressions to make sure the data is in the format you want, and then escape it with mysql_real_escape_string. Otherwise it can be very easy for someone to enter in a string of characters which can do very bad things to your database.

Here is a simple function which takes user input and makes it safe to display on the page. First it replaces any special html characters with their safe character codes. Then it checks magic quotes (to see if slashes were automatically added on input), and strips them if needed.

function fix_for_page($value){
    $value = htmlspecialchars(trim($value));
    if (get_magic_quotes_gpc()) 
        $value = stripslashes($value);
    return $value;
}

Magic quotes was created to make it easier for new PHP programmers to use user input. It automatically adds slashes when needed, but the problem is that it can cause even more confusion. This is why you need to first check to see if it is enabled, and process it accordingly.

This next function takes user input and makes it safe for use in a MySql query. It does this by stripping slashes (if needed), then using the mysql_real_escape_string function.

function fix_for_mysql($value){
    if (get_magic_quotes_gpc())
        $value = stripslashes($value);
    $value = mysql_real_escape_string($value);
    return $value;
}

It is a common belief that whether you are displaying user input on the screen, or using it in a query, the characters you need to escape are the same, but this is not true. That is why there are seperate functions. If you give data to a query that has only been checked using the standard special character and escape functions, there is still a chance for an exploit. Always use the proper safeguards.

Other uses of user input

Anytime you are including a file in your script, and the filename has been given by the user, you must be very careful. Imagine having this on your page:

<a href="template.php?the_file=section1.php">Section 1</a>
<a href="template.php?the_file=section2.php">Section 2</a>
<a href="template.php?the_file=section3.php">Section 3</a>

After looking at that code, a user might try to see if they can use the script to open some other file on the server, such as a configuration file with passwords. Since such a large number of scripts are insecure, there is a good chance that they would be able to. There are some good ways to deal with this though.

If you are working with a small number of files, you can always just make sure the input matches one of the options exactly, like this:

$pages = array('section1.php', 'section2.php', 'section3.php'); 
if (in_array($the_file, $pages) ) { 
    include($the_file); 
} else { 
    die("Invalid File!"); 
} 

Now if you have a lot of files, and they share a common naming system, you can use regular expressions to make sure the specified file name fits the naming scheme.

One other solution is to create a directory which contains the files, and only let the script open files in that directory. First, be sure to strip out any extra slashes in the user input, so that way the filename cannot be referencing another directory. Then hard-code the path like so:

$filename = "/data/" . $the_file;

That is a very basic example, but you get the idea. If you do not check for extra slashes, someone could try to open something like “../../../config.php“.

It is also a good idea to always use the .php extension for included files. That way, even if one gets opened in the browser directly, the code (data, passwords, etc..) will not be visible since PHP is processed on the server side.

PHP form mailers

The most commonly exploited PHP script is perhaps the form mailer. It is very rare to find one out there that does not have security holes. Many think that because the recipient’s name is hard-coded in the script, there is no way to use it to send mail to other people. This is completely false!

The most common way to exploit form mailers is through the injection of alternate email headers. Here is a very simple example. Imagine someone typing this in for the email subject:

Buy My Stuff!\\nBcc: someone_getting_spammed@domain.com

As you can see, a new header was injected into the message, causing a copy of the email to go somewhere else. Once again, this is an extremely simple example, as there are many other ways to inject email headers. Now that you are aware of the problem, you can take steps to fix it.

By now you know to always validate your data. After that step, you can do a simple check on your fields to make sure multiple lines were not added. Try this:

$from = $_POST["sender"];
if (eregi("\\r",$from) || eregi("\\n",$from)) {
    die("Invalid Input!");
}

You will need to perform this check on all of the fields (except the actual message). This is just one method. If you wanted, you could also strip out any colons from the fields since they are needed for headers.

You only really need to worry about this on your Name, Email, Subject, etc.. fields. The message itself is not as important (assuming you are using the mail function properly) since it is not put in the headers, but rather below them.

One alternative would be to have a field for the message and that’s it. Then no headers could get injected. Or you could still have the fields, but instead of putting the user input in the headers, it could just put it in the message itself.

There is much more to say about form mailer scripts, but that is all I will cover here. I will soon be writing an article focusing completely on PHP form mailers, which will include my personal script.

Conclusion

That concludes the first part of this article. Questions and/or comments are welcome!

Pages: [5] 4 3 2 1 » Show All

  1. Thank you for sharing your thoughts. I really
    appreciate your efforts and I am waiting for your further write ups
    thanks once again.

  2. This is details information must be sharing to the all the user thanks to the author for the supported keep up it. Thanks

  3. I just admire the hard work and nice procedures shared.

  4. Thank you so very much for taking the time to share…very useful, indeed!

  5. I just couldn’t leave your website before telling you that I truly enjoyed the top quality info you present to your visitors? Will be back again frequently to check up on new posts…

  6. The view limit is the number of times the page can be viewed before it automatically deletes. Leave blank to disable automatic deletion.

  7. nice post.Its really great resource for a newbies in this field like me. Thanks for sharing

  8. Thank you for this, came through google for something else
    but its good to know too while learning php. :D

  9. We really appreciate all the updates shared.

  10. Thanks for the information. I am starting to write a script that will update a MySQL database. Don’t want anybody doing fishy stuff with my site or resources.

  11. 9898989

  12. I just admire the hard work and nice procedures shared.

  13. ,khgfh I always gain new ideas here. very goo site. I give you 10 out of 10.
    .
    

Pages: [5] 4 3 2 1 » Show All

Leave a Comment

Before you comment: If you are having an issue with a script, please make sure you have read the entire article. Also, please read through the comments because most common issues have already been discussed many times. Thanks.


Be sure to wrap all code in <code></code> tags.