J.me

The Basics to Write a Secure PHP Web Application

Securing your application is the most important things when building an application. This is the basic that every programmer should follow, it’s a must. However, sometime programmer might forgot the basic, and the more complex your application is, the harder it is to maintain and looking for security holes. While securing your application doesn’t mean that you will be totally safe from a hack, since there is many factor of why a web can be hacked, but reduce the possibility is always a good practice.

This article will give you walkthrough on the basics of creating a secured PHP application. I will give some step by step to filtering input, keep your code up to date and standard. I’ll also give you some good practice I always do.

Filtering Input

A web application is operated by retrieving inputs from user and then decide what to do based of the inputs. Filtering your input is a must to prevent any unwanted code to get executed.

Making sure the input data type

This is the most basic you should take care of for filtering. For example if you are retrieving integer type, you will need to make sure you always get an integer. Here is some example of how to make sure you get an integer.

1
2
if ( ! is_numeric($_POST['number']) )
    die("You must input a number");
1
2
if ( $_POST['number'] != intval($_POST['number']) )
    die("You must input a number");

Mostly, input retrieved will be always have a string data type, so sometime, you will need to convert it to make sure you get a correct data type.

Validate the string

If you have a list of string you will be accepting, you will need to make sure you only retrieve this value. Here is some example you can do.

1
2
if ( $_POST['command'] != "edit" && $_POST['command'] != "add" )
    die("Unknown command");
1
2
3
4
5
6
7
8
9
10
switch ( $_POST['command'] )
{
    case "add":
    case "edit":
        // some code you will run
        break;
    default:
        die("Unknown command");
        break;
}
1
2
if ( ! preg_match('/^(edit|add)$/', $_POST['command']) )
    die("Unknown command");

I personally like the Regular Expression (regex) solution, since it is simple and need less work when you decide to accept a bunch of other strings. Also when you are accepting a pattern of string, it’s always good to use regex. Here is some example of basic email and URL filtering.

1
2
if ( ! preg_match('/^[A-Za-z0-9_.-]@[A-Za-z0-9_.-]\.[A-Za-z0-9]{2,4}$/', $_POST['email']) )
    die("You entered invalid email");
1
2
if ( ! preg_match('/^(http|https):\/\/[A-Za-z0-9_.-]\.[A-Za-z0-9]{2,4}$/', $_POST['url']) )
    die("You entered invalid url");

Remember that while PHP have two function to execute regex, POSIX Extended and PCRE, you must always use the PCRE solution as the other one has deprecated since PHP 5.3.0.

Escape your string

You should always escape your string before you insert it to your database. If your server have magic quotes turned on by default, you might be safe from this. But your code shouldn’t rely on server capabilities, your code should escape the string if the magic quotes is turned off. You also must check the magic quotes configuration to prevent you to escape your string twice.

1
2
if ( ! get_magic_quotes_gpc() )
    $name = addslashes($_POST['name']);

If you are using MySQL database, use mysql_real_escape_string instead.

1
2
if ( ! get_magic_quotes_gpc() )
    $name = mysql_real_escape_string($_POST['name']);

Finally, if you don’t want to accept html string, you can strip it or convert it to html entities.

1
$no_html = strip_tags($_POST['no_html']); // this will strip all html tags
1
$no_html = htmlentities($_POST['no_html']); // this will convert all html tags to entities

Wrap it out in a function

Making your code easier to maintain is one way to secure your application. Wrap it out in a function is a good solution. All you need to do is making sure the retrieved input has filtered in this function. So if you find any vulnerabilities, the first you will look is the function. This should save your time looking for the security holes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function input_filter( $input, $type = '' )
{
    $filtered_input = $input;
    if ( get_magic_quotes_gpc() )
        $filtered_input = stripslashes($filtered_input);
    switch ( $type )
    {
        case 'numeric':
            $filtered_input = intval($filtered_input);
            break;
        case 'email':
            $filtered_input = ( preg_match('/^[A-Za-z0-9_.-]@[A-Za-z0-9_.-]\.[A-Za-z0-9]{2,4}$/', $filtered_input) ) ? $filtered_input : '';
            break;
        case 'url':
            $filtered_input = ( preg_match('/^(http|https):\/\/[A-Za-z0-9_.-]\.[A-Za-z0-9]{2,4}$/', $filtered_input) ) ? $filtered_input : '';
            break;
        case 'html':
            $filtered_input = htmlentities($filtered_input);
            break;
    }
    return mysql_real_escape_string($filtered_input);
}

And the example to use this function.

1
2
3
4
5
$number = input_filter($_POST['number'], 'numeric'); // it will return 0 if it is not numeric
$email = input_filter($_POST['email'], 'email'); // it will return empty string if invalid
$url = input_filter($_POST['url'], 'url'); // it will return empty string if invalid
$no_html = input_filter($_POST['no_html'], 'html'); // it will return all html converted to entities
$string = input_filter($_POST['string']); // it will simply escape the string

Depends to your preference, you can wrap it to a numbers of separated functions or to a class. But I think wrap it in a function is not bad either if you are creating a simple application and only need a small numbers of filtering.

Keeping Your Code Up To Date and Standards

PHP keep growing. If you are writing your application in PHP 4, most likely, it will still working in PHP 5. However, some of them might not work anymore in PHP 6. Some example is the use of deprecated function. The function that deprecated in PHP 5 will still working for backward compability, but using this function is not safe anymore as they can be removed anytime in future.

Make sure you are up to date to the new PHP version. Always look the PHP manual for each function you use. Some time you can find a notice of how to correctly use the function. The user contributed notes is also helpful if you are looking for a solution related to that function.

Do not rely on register globals

Register globals has been turned off by default and deprecated since PHP 5.3.0. Incorrect use of register globals will make your code vulnerable. Always use superglobal variable instead.

However, it’s also a good practice to not use $_REQUEST variable if you don’t need to. This variable contains the content of $_GET, $_POST and $_COOKIE. Using this variable will prevent you to know where the variable came from, it’s always good to know where your variable came from. For example you would like some inputs came from post or cookie, but using $_REQUEST, the user can use get and it still get executed.

Always define a variable before use it

You must always define your variable before you use it. In some server, register globals might be turned on, and failing to do so might result in vulnerable codes.

1
2
3
4
if ( validate_user() )
    $safe = true;
if ( $safe )
    // some sensitive code

The above code is vulnerable if register globals is turned on. If user run the script by calling user.php?safe=1, the user will always get validated. You can do this instead.

1
2
3
4
5
$safe = false;
if ( validate_user() )
    $safe = true;
if ( $safe )
    // some sensitive code

Use strict error reporting in development

Checking each function you use to find a deprecated function is not fun. So in development environment you must show all PHP errors. You can do this by calling this function on top of your application.

1
error_reporting(E_ALL);

This way, you will know if you are using deprecated function. You will also know if there is any undefined variable. This also includes the array in superglobals variable. Making sure to check undefined variable with isset before you use it is also a good practice.

Turn off all error reporting in production

If your website is ready now, you will need to turn off all error reporting. Showing your error to everyone will not be a good thing, one thing is you will reveal something to your visitor, such as file structure. For example, your main code might not be vulnerable, but some of included codes might be vulnerable if requested directly. Keeping your file structure a secret might give you a better security.

One thing to note, you also shouldn’t show mysql_error value to your visitor. If somehow you don’t filter the inputs properly and you have a code like this.

1
$query = mysql_query("SELECT * FROM some_table WHERE some_field='$field_value'") or die(mysql_error());

The code above might look familiar as new programmer tends to use it to check for query error. However, if the $field_value variable is not filtered properly and vulnerable to injection, you will show the visitor the error and at the same time, you will reveal that your code is vulnerable. So instead of showing the error, you must log the error instead, this way only you can see the error.

Conclusion

So this is the basic you can use to secure your web application properly. Meeting this standard will make your application less vulnerable. Again, it will not guarantee that your application will be perfectly safe, as there is many other factors such as using an outdated web server or using some vulnerable library.

Hopefully, this will be useful for you. Do you also have a technique to write a secure web application? Please share in comment.

Final words, thank you to read this. 🙂

3 comments | Leave a comment

  1. takien September 3, 2010 15:56

    Nice post bro.., I do it 😀
    Additionally, I always create unique hash (something like wp-nonce) and or referrer check to ensure the action operated from the right place by the right person :ngakaks

    • keaglez September 4, 2010 03:35

      Thanks takien for sharing the tips. 🙂

      That’s a good way to prevent spoofing attack.

  2. geleAmbitte February 20, 2011 18:51

    thanks for this nice post 111213

Leave a comment