Skip to main site navigation
Skip to main content
Switch to text-only view
Switch to default view
Thumbdrive in computer

Basic PHP Security Measures

This guide is not intended to be an exhaustive compilation of measures that one should consider when securing a web application. You should always spend time researching security at greater length.

Note: This page contains sample script for securing php scripts.
When using, change all variable names to something unique per application, per instance.

TOPICS

BASIC PRINCIPLES

The following lists basic principles to consider when securing web applications.

  • Limited Access: Only give your visitors access to those files which they actually need access to.
  • Security in Layers: It is advisable to add security measures, even if other measures seem to accomplish the same thing.
  • Make secure coding part of your regular practice, and not an after thought.
  • Encrypt sensitive data.
  • Sanitize data coming and going, no matter the source.

SANITIZING FORM INPUT

Cleaning input before using data in a MySQL database:
The following is a basic function which helps sanitize form inputs destined for a MySQL database.

NOTE: This function only works when there is a valid MySQL database connection made.
   function make_clean_general($var) {            
                return mysqli_real_escape_string(trim($var)); }   
This is an example of how to apply the function:
$fromForm = make_clean_general($_POST["input"]);

Verify input data:
If you know what type of data to expect, you can assure that only that type of data is entered.

For example, use the PHP function 'stripslashes':
$comment=stripslashes($_POST["comment"]);

Assuring that input is only numeric could be done with the use of the php function 'integer':
Sample script:


$variable = (int) $_POST['FormData']; // turns input in to a non-decimal number.
OR
$variable = $_POST['field'];
IF ( !is_numeric($variable)) exit ("input is invalid"); // verifies input is a number or dies.

Use of php function 'checkdate' checks for valid date:
$true = checkdate(01, 31, 2000); // outputs '1'

Checking Length:
If the input length is known, you can also check to see if the input length is correct using the function strlen().

For example, suppose you require all usernames to be less than 8 characters, and you have an input field for username. You can check the input length to verify that it is not longer than expected:

If ( strlen($variable) >= 9) exit ("Invalid entry.");

Additionally, PHP comes with type checking functions to validate input type:

Ctype checking functions:

  • ctype_alnum — Check for alphanumeric character(s)
  • ctype_alpha — Check for alphabetic character(s) * ctype_cntrl — Check for control character(s)
  • ctype_digit — Check for numeric character(s)
  • ctype_graph — Check for any printable character(s) except space
  • ctype_lower — Check for lowercase character(s)
  • ctype_print — Check for printable character(s)
  • ctype_punct — Check for any printable character which is not whitespace or an
    alphanumeric character
  • ctype_space — Check for whitespace character(s)
  • ctype_upper — Check for uppercase character(s)
  • ctype_xdigit — Check for character(s) representing a hexadecimal digit

Example code for Ctype functions:

$variable="abc123"; 
    if (ctype_alpha($variable)) {
             echo "The string $variable consists of all letters.\n";}
                 else {echo "The string $variable is not all letters.\n"; }       
                 // would display that $variable is not all letters, as it has numbers
Or you can use type casting for numeric input when calling form data:
$variable=""; $variable = (int) $_GET['formData'];

- (int) stops accepting input the moment it finds a non-numeric character.

  • For example, if formData = 12a34, or 12.34, $variable would equal 12
  • If the first character is not numeric, $variable would equal 0 (zero).
  • Also useful when calling db entry via integer type primary key:
    $SQL = 'SELECT * FROM table WHERE id = '(int)$id';

The following are cast types in PHP:

  • String - (string)
  • Boolean - (bool), (boolean) * Integer - (int), (integer) * Binary - (binary) [PHP 6] * Floating Point - (float), (double), (real)
  • Array - (array) * Object - (object)

Other special functions for controlling data:

  • $_GET['userid'] = str_replace("'", "", $_GET['userid']); //Removes all ' symbols from the userid string
  • $_GET['userid'] = str_replace(" ", "", $_GET['userid']); //Removes all spaces from the userid string.
  • $_GET['userid'] = trim( htmlspecialchars(addslashes($_GET['userid'])) ); //Returns a string with backslashes before special characters and changes special characters (for example - from '&' to '&')
Return to top

$_SESSION Security

A session is started everytime a user visits your page.
One way to help prevent session hijacking is by creating a unique "token" for each user to verify that each instance of code sent to a user is validated per user session. This is useful where you are sending a user to another webpage, or using the 'Switch' function on the same webpage.

Start off by placing this code at the very top of each page:
<?php if( !isset( $_SESSION ) ) { session_start(); }

On the first page, or first section, place this code:
$token="";
$token=md5(uniqid(rand(), TRUE));
$_SESSION['token']=$token;

You can also add this code if you want to time out a session.
$_SESSION['token_time'] = time();

On the second page, or section section of the same page, add this code:
if ($_SESSION['token'] == $_POST['token'])
{
In here is the script for the second page or next 'Switch' item.
}

Additional Thoughts:

Use of preg_match if you know what the input should be. The \b in the pattern indicates a word boundary, so only the distinct word "WORD" is matched, and not a word partial like "WAYWORD".


Sample script:

$compare1="right WORD";
$compare2="right WORDS";

if (preg_match("/\bWORD\b/i", $compare1)) {
    echo "A match was found."; }
  // because it found the exact word 'WORD' in $compare1
   if (preg_match("/\b$WORD\b/i", $compare2)) {
    echo "A match was *not* found."; }
     // because it did not find the exact word 'WORD' in $compare2
          // in this case, it *would* match where the IF statement were:
       if (preg_match("WORD", $compare2))
         // because it found "WORD" as a form of "WORDS".  
           // Similarly, the words 1WORD, FORWORD, 2WORD8, would all match as a form of "WORD".
Return to top

SANITIZING OUTPUT TO PREVENT CROSS-SITE SCRIPTING (XSS)

There are 3 common output entities: databases, client, and URLs, the most common being clients.

The PHP function htmlentities() is best for escaping data being sent to clients.
Example code:

   $variable=array();
    $variable['data'] = htmlentities($variable['data'], ENT_QUOTES, 'UTF-8');

 // an advanced version of this will strip non-UTF-8 characters
$variable['data'] = htmlentities(iconv('UTF-8', 'UTF-8//IGNORE', $variable['data'], ENT_QUOTES, 'UTF-8'); echo $variable . " is a value in the database.";
// note: ENT_QUOTES translates single and double quotes, and UTF-8 is the specified character set used in conversion.
To create a function to perform this task, consider:
    function send_safe($variable ) 
{        htmlentities( $variable, ENT_QUOTES, 'UTF-8' );
         return $variable;  }
And use similarly to: $toSend=""; $toSend=$row['sample']; // where data was retrieved from database. echo send_safe($toSend); // where you are now sending data to the browser.
Return to top

SECURITY AND INCLUDES

Don't store a configuration include at the directory level of your application. Store them in a separate folder not easily accessed. Avoid easy to guess file names.

Protecting the Call:
The idea is to set a variable or constant with a value you expect for both calling an include, and verifying the called include. Use this on the calling page, (ie...to include 'calledPage.php' but prevent unauthorized include):

   if(!defined("ECHO")){define("ECHO", "called");}         
// "called" should be changed to something unique to your script     
include('calledPage.php');
if ( defined('RESPONSE') != "response" ) // "response" value to match value on called page. {die("Hacking attempt");}
Use this on the called page:
// First see if constant is defined as expected if ( defined("ECHO") != "called" )
// "called" value to match value on calling page. {die("Hacking attempt");}
// Now define the response constant if not already defined if (!defined("RESPONSE")){define("RESPONSE", "response");} // "response" should be changed to something unique to your script
NOTE: If you are using a variable to define an include path, use a constant instead.
Return to top

SECURING the SWITCH FUNCTION

If you use the switch function, you already know which inputs you are looking for.
For example, you may have switch() cases, 'input', 'add', 'delete'.
Before acting on the switch, verify that the case is one that you expect, examples code:
   $variable="";    If ($_POST['todo'] == "input" OR "add" OR "delete")    
{$variable = $_POST['todo'];}
// or better yet, since you know the input should be alphanumeric in this example:        
 {$variable = ctype_alnum($_POST['todo']);}     
else    {$variable = "default";}        
// and then assign a case for default, even if it does nothing.  
For increased security, only use letters as switch cases so that you can type verify the input.
Return to top

A NOTE ON ELEVATED PRIVILEDGE PAGES

If you have a page which elevates a user privilege, (for example, a user is sent to an admin page), open a new page and change the SESSION ID for that page. Script example:
   session_start();
    If (!isset($_SESSION['initiated'])) 
     {session_regenerate_id();
       $_SESSION['initiated']=TRUE; } 

A NOTE ON INSTALLING SOMEONE ELSE'S SCRIPTS

If the script came with its own installer, make sure unnecessary install files are deleted after installation. Always change default passwords and security variable definitions.
Return to top

Things NOT to do

  • Never give a file name a non-'.php' extension, such as ".inc". If you must have ".inc" in the extension, use the extension ".inc.php", as that will ensure the file is processed by the PHP engine.
  • DON'T store your includes in your web root and named something obvious.
  • NEVER use $_REQUEST. Also, $_POST is preferred over $_GET.
  • NEVER allow A user to input file names. If asking a user to select a file, use A drop down menu AND $_POST.
  • DON'T use the magic_quotes_gpc function.
  • DON'T store old copies of files on the web server. If you absolutely need to, name them with a same file extension, and exclude access to that file extension via .htaccess.
  • Avoid using the eval() function.
  • NEVER include scripts from remote servers.
Return to top

Things TO do

  • Always add a blank file named "index.html" to all non-indexed folders like those storing includes or image folders.
  • Always initialize variables and arrays, ($variable=""; or $array=array();).
  • Use $_POST as opposed to $_GET, and never use $_REQUEST.
  • Remove any error generating debug code before going in to production, especially database sql debug error code.
  • Use mysqli_real_escape_string() function to filter input destined for a MySQL databases. (example)
  • Use pg_escape_string() to filter input destined for a PostgreSQL database.
  • Use htmlentities() function on data being sent to users, (such as when using the 'echo' command). (example)
  • Always encrypt sensitive data and passwords, and use an application specific salt.
  • Restrict access to only those users who need access.
  • If using a variable to define the path of an include, sanitize the path variable before using.
  • Secure Switch() functions by following the guide on this page. (example)
  • Turn off error reporting before going in to production. error_reporting(0);

Other considerations:

  • Use checkdate() function to verify form input dates. (example)
  • Use stripslashes() function to help sanitize general form input. (example)
  • Use other forms of input checking where appropriate.
  • MD5 should be combined with an application specific salt.
  • Use SSL to mitigate man-in-the-middle sniffing.
  • If you allow users to specify URIs, (say to show images/avatars), use the parse_url() function.
  • If you allow the uploading of a file, set a size restriction in the HTML code and verify size input with filesize() function.
  • Consider using strlen() variable to verify that input size is not larger than expected. (example)