Elliott Brueggeman - PHP and Web Development Info, Photography, and More
 
Home | Web Dev Blog & Articles | PHPGraphLib | PHPSimpleChat | SkinnyTip | PHPWeatherLib | Photography | Contact
Posted on December 8, 2008 in MySQL, PHP by Elliott BrueggemanNo Comments »

Security is an important part of PHP programming, and PHP provides several tools for securing database queries and HTML display. However, knowing which function to use and when to use it can be somewhat confusing, as there’s many details to pay attention to. It’s important not to leave your website open to cross-site scripting or SQL injection attacks.

This example and explanation will focus on a common web scenario – a user submitting data via a form, being asked to confirm it, and then being displayed the data after it is stored in the database.

1. Initial User Input

In this scenario, a user will be presented an input box for which they can add a review about a particular product on a public facing website. The user is not allowed to use html, similar to the restrictions on product reviews on Amazon.com. The user inserts their review into the input box and then clicks submit.

2. User Input Preview and Cross Site Scripting Prevention

Now, we want to show the user their review and have them confirm it before we add it to the database. This is a prime example of a situation that leaves a website open for a XSS (cross-site scripting) attack. We don’t know what the user entered on the previous page, and they could be purposely entering malicious content. Because what they entered in going to be output to the screen, if they input raw PHP code, it could get executed unknowingly by our server. To prevent this, we need to html encode all potentially dangerous characters for display on the screen.

I would recommend using htmlentities() to do this, and use the ENT_QUOTES option as shown below, which means that both single and double quotes will also be encoded. Because we also decided that we wouldn’t allow HTML, we’ll want to strip that HTML before displaying the results, to give the user an accurate preview. Before doing any of this, we’ll have to grab the submitted data from the submitted form – this tutorial won’t cover this, but there are plenty of web tutorials available for doing this.

<?php
 
//strip HTML tags from input data
$input_data = strip_tags($input_data);
 
//turn all characters into their html equivalent
$preview_data = htmlentities($input_data, ENT_QUOTES);
 
//...display $preview_data 
 
?>

3. Database Insert and SQL Injection Protection

Now, let’s say the user previews above data, and clicks accept, which will send this data to another script for entry into the database. Protecting your database from SQL injection requires different steps than protecting against cross-site scripting.

It is very likely that you don’t want to store the user’s data in HTML encoded form. Let’s say you are using a varchar(32) column in your database, and you had an input box that was 32 characters long. If you were trying to store the HTML equivalent of this in your database, you would need a column that was much larger to guarantee that data didn’t get lost. This is because the HTML equivalent of a single character is often many characters long. For example, a double quote (”) becomes ", and an ampersand (&) becomes &. We need to guard against single quotes, because these can cause an SQL injection problem, depending on how your database is setup. Consider this example:

<?php
 
$name = "George'); DELETE FROM mytable; INSERT INTO mytable (name) VALUES ('you got hacked";
 
$sql = "INSERT INTO mytable (name) VALUES ('$name')";
 
//...run the $sql query
 
?>

If your SQL server allows more than one SQL command on a single query request, you’ve just lost lots of data. While it’s a good idea to lock down your database server so it doesn’t allow this, you always want to secure your code independently of the database, in case you change your hosting setup later.

To secure your script, you can use the addslashes() function which will escape both single and double quotes, by adding backslashes before them, to prevent multiple queries from being executed. You’ll also want to test for the length of the input field, as forged form requests are easy as can be using tools like Firebug. Here is example code that will accomplish that:

<?php
 
//escape trouble characters
$name = addslashes($name);
 
//make sure not longer than expected length
$name = substr($name, 0, 32);
 
$sql = "INSERT INTO mytable (name) VALUES ('$name')";
 
//...run the $sql query
 
?>

Note that there is the possibility of valid user input being cut off when being inserted into the database. If the user used all 32 characters in the input box, and a single quote was one of them, then the above code would trim off the last character. You may want to prevent this by using a 28 max character input box for a varchar(32) column, giving the user 4 opportunities to use an escaped character without having their input cut off.

4. Retrieving and Displaying the Data From the Database

Now that the data is safely in the database, everything is safe right? Wrong. We elected not to store HTML encoded data from users. This means that we are still at risk from a Cross Site Scripting attack every time we query and then display the data. What you’ll probably want to do is create a function that sanitizes database data before being displayed on the screen. No only should you HTML encode the data, but you’ll also want to remove the backslashes you added earlier, as these aren’t meant to be displayed. Here’s what your function could look like:

<?php 
 
function sanitize_data($input_data) {
  return htmlentities(stripslashes($input_data), ENT_QUOTES);
}
 
?>
Posted on August 24, 2008 in PHP by Elliott Brueggeman3 Comments »

Having a secure area to a PHP site is a common website requirement. Implementing the secure area, on the other hand, can be difficult and confusing. I’ve put together a basic security framework that you can use for your PHP based website.

What this framework is:

  • Simply coded & easy to understand
  • Secure enough for most sites, blogs, and internally accessed (intranet) sites.

What this framework is NOT:

  • Secure enough for sites involving e-commerce transactions and sensitive personal information.

Note the “NOT” clause above – this implementation is not for securing sensitive data. Most people implementing this framework will be doing it on unsecure sites (sites NOT using the HTTPS secure socket layer), causing user passwords to be sent from script to script in plaintext, which is vulneable to intrusion.

How the PHP security flow works

This framework works as follows:

  • User submits login form containing name and passsword to login processor script
  • Login processor form compares name and password to stored value
  • If name and password do not match expected value, send user back to login form
  • If name and password match expected value, set a session variable containing a unique hash value, and forward user to secure page

When a user tries to access a secure page, this happens:

  • Security code checks session variable with user hash and compares to expected value.
  • If the hash values match, continue displaying page.
  • If the hash values do not match, send user to login form.

Seems simple right? It is! To implement the above workflow you’re going to need to use the code I have included below.

Login Form

Below is the login form code. Note that you must set the destination of the form to match the name of your check login script (login processor.)

<?php
/*
* FILE: login.php
*/
?>
<html>
<head>
  <title>Login</title>
</head>
<body>
  <form name="login-form" method="post" action="check_login.php">
    <p>User: <input type="textfield" id="user" name="user"/></p>
    <p>Password: <input type="password" id="pass" name="pass"/></p>
    <p><input type="submit" name="Submit" value="Submit"></p>
</form>
</body>
</html>

Login Processor

The login processor has the main chunk of your security code. This is the script that decides whether you have a correct username and password, and what to do after logging in. Note that this code uses an include of login functions to accompish its purpose. These login functions are shown later in this article.

<?php
/*
* FILE: check_login.php
*/
 
session_start();
 
//include our login functions.
require('login_functions.php');
 
//retrieve post data
$user = trim($_POST['user']);
$pass = trim($_POST['pass']);
 
 
/*
* Basic Login Logic
*/
 
clear_login_state();
 
if (!empty($user) && !empty($pass)) {
 
  if (check_login_correct($user, $pass)) {
 
    //set appropiate session vars
    login_user($user);
 
    //redirect to secured page
    send_to_page('secure_page.php');
  }
  else {
    //wrong user or password supplied, send back to login
    send_to_page('login.php');
  }
}
else {
  //no user or password supplied, send back to login
  send_to_page('login.php');
}
?>

Login Functions

The above Login Processor code uses some important functions to accomplish the secure login. These functions are enclosed in their own file and included by the above code.

<?php
/*
* FILE: login_functions.php
*/
 
function check_login_correct($user, $pass) {
  /**
  * This function is for you to fill in.
  * Typically, you would compare the user's password 
  * to the password stored in the database, and then return
  * either true or false, depending on the result.
  */
 
  if ($user == 'admin' && $pass == 'Chelsea') { return true; }
 
  return false;
}
 
function login_user($user) {
  session_regenerate_id();
 
  //set the user session variable, for later app use
  $_SESSION['user'] = $user;
 
  //set the hash session variable
  $_SESSION['hash'] = calculate_secure_hash($user);
}
 
//function sends the user to a page. Note this must be called 
//in the header, before any page output (echo's, html, print, etc) 
function send_to_page($page) {
  header("Location: $page");
  die("Redirect Failed");
}
 
//clears login state (logs you out) by unsetting login variables
//must be called in header, before any page output (echo's, html, print, etc) 
function clear_login_state() {
  session_unset();
}
 
function calculate_secure_hash($user) {
  //the security of your system is based on the hash seed below - change often
  $hash_seed = 'this_is_a_secret';	
  return md5($_SERVER['HTTP_USER_AGENT'] . $hash_seed . $user);
}
 
function check_logged_in() {
 
  //retrieve session vars
  $found_hash = $_SESSION['hash'];
  $user = $_SESSION['user'];
 
  //must not be empty
  if (!empty($found_hash) && !empty($user)) {
 
    //recalculate the hash
    $calculated_hash = calculate_secure_hash($user);
 
    //if recalculated hash matches, we have a logged in user
    if ($calculated_hash != $found_hash) {
      send_to_page('login.php');
    }
  }
  else {
    send_to_page('login.php');
  }
}
 
?>

Secure Page Code

Okay, so we have successfully logged a user in – now what? Well, for each secure page accessed you have to reverify the hash value. On each secure page, you’re going to need to setup the top of your page (PHP script) like this:

<?php
/*
* FILE: secure_page.php
*/
 
session_start();
 
//include our login functions.
require('login_functions.php');
 
//do security check
check_logged_in();
 
//now, display the page's content...
echo "You are viewing a secured page!";
 
?>

Download the Above Code

You can download this zip of the above code, and run it on your server to test how the authentication code works. Remember, you’re going to need to customize the check_login_correct() function to fit your particular password storage method.