More Secure Form Validation


In this book, form validation has been discussed several times, using different methods. The golden rule of validating any data received by a PHP page is to assume that it's invalid until it passes the right tests indicating otherwise. At a bare minimum, you should

  • Use the superglobals (e.g., $_POST['name']) rather than the registered globals ($name).

  • Check text, password, and textarea form inputs for values using empty().

  • Check other form inputs for values using isset().

  • Check any form input by verifying that it has a positive length.

A better way to validate data is to see if it conforms to a certain type (like an integer), as will be covered shortly. An even more exacting method of form validation requires the use of regular expressions, discussed later in this chapter. You can also use JavaScript to perform basic validation on the client (within the Web browser) before the data is sent to the server. This will also be discussed in this chapter. But first, there's an entirely different kind of validation you can use: validating that a form has only been submitted once and that the right form has been submitted to a page. These first two topics are the focus over the next few pages.

Preventing multiple submissions

A common question I see is how to prevent someone from submitting the same form multiple times. Whether a user repeatedly submits a form on accident or on purpose, such occurrences can be a minor nuisance or a major problem for your Web site. There are many different ways to prevent multiple submissions, and I'll discuss two options here.

First, if you are already using sessions, an easy solution is to create a session variable indicating whether a specific form has been submitted or not.

 if (isset($_SESSION['form_name'])) {   // Do not handle the form. } else {   // Handle the form.   // Indicate that the form   // has been handled.   $_SESSION['form_name'] = TRUE; } 

This technique is both effective and simple but does require the use of sessions.

In the following steps, I'll demonstrate another option, which is viable for applications that use a database to store the submitted information. The premise is this: A generated identifier will be stored in the HTML form (as a hidden input). This value will be inserted into the database (see the sidebar on page 384) along with the other submitted information. To prevent repeated submissions, this identifier can be stored in the database only once. A user wishing to submit the form again will have to reload the HTML form so that another unique identifier is created.

To prevent multiple form submissions

1.

Begin a new PHP document in your text editor or IDE (Script 10.1).

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head>   <meta http-equiv="content-type"      content="text/html; charset=     iso-8859-1 />   <title>Enter Your Comments</title> </head> <body> <!-- Script 10.1 - comments.php --> 

Script 10.1. PHP is used in this basic HTML form to store a random key in a hidden form input.


2.

Begin defining the form.

 <form action="handle_comments.php"   method="post>   <fieldset><legend>Enter your     comments in the form below:     </legend>   <p><b>Name:</b> <input type="text"      name="name size="20"      maxlength="40 /></p>   <p><b>Comments:</b> <textarea     name="comments rows="3"     cols="40></textarea></p>   </fieldset>   <div align="center"><input     type="submit name="submit"     value="Submit /></div> 

I'm keeping this form simple, taking only the user's name and comments.

3.

Store a unique identifier in a hidden form input.

 <input type="hidden" name="stamp"  value="<?php echo md5(uniqid  (rand(), true)); ?>" /> 

To achieve the specific goalavoiding duplicate form submissionsthis is the most important line. Each time this page is loaded, a unique 32-character stamp is generated. Doing so requires use of the rand(), uniqid(), and md5() functions. Of these, uniqid() is the most important; it creates a unique identifier. It's fed the rand() function to help generate a more random value. Finally, the returned result is hashed using md5(), which creates a string exactly 32 characters long (a hash is a mathematically calculated representation of a piece of data). You do not need to fully comprehend these three functions, just note that the result will be a unique 32-character string.

4.

Complete the form and the page.

 </form> </body> </html> 

5.

Save the file as comments.php, upload to your Web server, and run in your Web browser (Figure 10.1).

Figure 10.1. The HTML form looks much like any other.


Note that this has to be a PHP page, not an HTML one, as it has a line of PHP code.

6.

View the HTML source to see the value of the hidden stamp (Figure 10.2).

Figure 10.2. The source of the HTML form reveals the PHP-generated stamp.


7.

Reload the page, and then review the HTML source to confirm that the stamp value has changed (Figure 10.3).

Figure 10.3. Each running of the same form should create a different stamp value.


Now that the form itself has been written, a quick PHP page will be made that will handle the form data.

To write handle_comments.php

1.

Begin a new PHP document in your text editor or IDE (Script 10.2).

 <!DOCTYPE html PUBLIC "-//W3C//DTD   XHTML 1.0 Transitional//EN "http://www.w3.org/TR/xhtml1/DTD/  xhtml1-transitional.dtd> <html xmlns="http://www.w3.org/1999/  xhtml xml:lang="en" lang="en"> <head>   <meta http-equiv="content-type"     content="text/html; charset=    iso-8859-1 />   <title>Handle Comments</title> </head> <body> <?php # Script 10.2 - handle_  comments.php 

Script 10.2. Because the stamp value in the database has to be unique, this script will not allow the same form data to be recorded twice.


2.

Include the database connection script.

 require_once ('../mysql_connect.  php); 

I haven't included this script in this chapter, but it will be very similar to the one created in Chapter 8, "Web Application Development." Besides connecting to the MySQL server, it should select the test database and define the escape_data() function.

3.

Validate the name and comments fields.

 if (!empty($_POST['name'])) {   $n = escape_data($_POST['name']); } else {   echo '<p><font color="red">You     forgot to enter your name.    </font></p>';   $n = FALSE; } if (!empty($_POST['comments'])) {   $c = escape_data($_POST    ['comments]); } else {   echo '<p><font color="red">You     forgot to enter your comments.    </font></p>';   $c = FALSE; } 

To validate the person's name, I check that the name value isn't empty. If it isn't, then the value is escaped using the escape_data() function and assigned to the $n variable. Otherwise, an error message is printed and $n is set to FALSE. This process is repeated for the comments.

4.

Validate the stamp.

 if (strlen($_POST['stamp']) == 32 ) {   $s = escape_data($_POST['stamp']); } else {   echo '<p><font color="red">This     page has been accessed in error.    </font></p>';   $s = FALSE; } 

Just because the stamp value is a hidden input generated by PHP doesn't mean it shouldn't be validated. The stamp value should always be 32 characters longas the md5() function always returns a string of that lengthso I can be precise in my validation routine. Although the submitted stamp shouldn't contain any problematic characters, you can never be too careful, so the value is also run through escape_data().

5.

If all tests passed, attempt to insert the new record into the database.

 if ($n && $c && $s) {   $query = "INSERT INTO comments     (name, comment, stamp) VALUES     ('$n, '$c', '$s')";   $result = @mysql_query ($query); 

If all three tests were passed, then $n, $c, and $s will all be true, making this conditional true. Inside the conditional, a simple INSERT query is defined and executed. The error suppression operator is used here, and any errors that occur will be handled later in the script.

6.

Report on the results of the query.

 if (mysql_affected_rows() == 1) {   echo '<p>Thank you for your     comments.</p>'; } else {   echo '<p><font color="red">Your     comments could not be added.    </font></p>';   echo mysql_error() . '<br /><br />    Query: ' . $query; } 

If one row was successfully inserted, then mysql_affected_rows() will return 1, and a thank-you message is printed. If one row was not inserted (or affected), an error occurred. For debugging purposes only, I'll print out the MySQL error and the query itself.

While developing the script, you may have errors because of improper query syntax, referring to a table or column that doesn't exist, and so forth. Once you've worked out these quirks, the only error that should occur is if the same form is submitted multiple times. In such cases, MySQL will cough up a duplicate key error (Figure 10.4), which is actually what you want. Naturally in a live site you would not reveal this information to the end user.

Figure 10.4. Resubmitting the same form with the same stamp value will cause a MySQL error.


For a live site, you could also change the public error message to something like Your comments have already been processed.

7.

Complete the main conditional.

 } else {   echo '<p><font color="red">Please     go back and try again.</font>    </p>'; } 

If $n or $c or $s is not true, then this message is printed.

8.

Close the database connection and complete the page.

 mysql_close(); ?> </body> </html> 

9.

Save the file as handle_comments.php, upload it to your Web server (in the same directory as comments.php), and test by submitting the HTML form (Figure 10.5).

Figure 10.5. If the form is submitted for the first time, meaning that a unique stamp value was used, the data will be inserted into the database and the user will be thanked.


Tips

  • There's no way using PHP that you can determine a form's name. The only option is to store that value in a hidden form input.

  • If you're comfortable with JavaScript, you can add JavaScript code to your form to ensure that it is submitted only once. Search the Web for examples.

  • As a reminder, it's more secure to use the POST method with forms than the GET. If your form includes passwords or other sensitive information, you really must use POST.

  • Remember that hidden inputs in forms are still viewable in the HTML source and therefore aren't a secure way of temporarily storing information. You should never store a password or other secret information there.


Validating the right form

PHP programmers often spend a lot of time fixating on form dataand rightly sobut do not think about the forms themselves. Say you have, as part of your site, a contact.html page with a form that is handled by contact.php. The assumption is that contact.php will be receiving data from contact.html, but that's not necessarily the case.

The fact of the matter is that a malicious user could create their own HTML page with a form whose action attribute is http://www.yoursite.com/contact.php. Using this form, they could send any data of any type to your page. If your handling page isn't looking for specific form fields and data, this could be a major security concern.

In this example I'll show a nifty little trick for validating that the received form data matches what is expected. The premise is that you make a list of what form inputs the page should receive and then check for a match. I'll rewrite the handle_comments.php using this technique.

Using the Test Database

MySQL creates two databases as part of its normal installation process. The first, called mysql, is the most important. MySQL access permissions are controlled by this database.

The second database is called test. This database, appropriately enough, is intended for testing purposes. It is somewhat unique in that anyone has permission to access this database (you do not need to establish a username/password combination for it). Since this first example is unrelated to any other project, I'll create a new table within test. Here is the CREATE statement required to make the table:

 CREATE TABLE comments (    comment_id INT UNSIGNED NOT NULL      AUTO_INCREMENT,    name VARCHAR(60) NOT NULL,    comment TEXT NOT NULL,    stamp CHAR(32) NOT NULL,    date_entered TIMESTAMP,    PRIMARY KEY (comment_id),    UNIQUE (stamp) ) 

To make things easier, I'm storing the person's name in one field, but I generally recommend that you use separate first and last name columns. Also, a UNIQUE index is placed on the stamp column, which will help prevent duplicate submissions.


To validate a form

1.

Open handle_comments.php (Script 10.2) in your text editor or IDE.

2.

After the initial PHP tag, define what form inputs are expected (Script 10.3).

 $allowed = array('name', 'comments',   'submit, 'stamp'); 

Script 10.3. By referring to the keys of the $_POST array, you can confirm if all the correct inputs were submitted to this page.


Using the array() function, I create an array of allowed inputs. Remember that you must include the submit input here, as that will also be passed to this page. Also, the inputs should be listed in the same order as they appear in the form and have the exact same name (so submit, not Submit).

3.

Assign the received variable names to a new array.

 $received = array_keys($_POST); 

This functionarray_keys()returns the names of the keys for a given array. In this case, it should return name, comments, submit, and stamp (in that order), assuming that the proper form has been submitted to this page.

4.

Create a conditional that checks if the two arrays are the same.

 if ($allowed == $received) { 

You can easily compare one array to another using the equality operator. This conditional merely checks that the received keys (the form input names) exactly match the expected keys. If that's the case, the form can be handled as it was before.

5.

After the mysql_close() line, complete the $allowed == $received conditional.

 } else {   echo '<p><font color="red">This    page has been accessed in error.   </font></p>'; } 

If the submitted inputs do not exactly match the expected inputs, the form is not handled and this error message is printed.

6.

Save the page as handle_comments.php, upload to your Web server, and test in your Web browser (Figure 10.6).

Figure 10.6. If the right form with the right inputs was submitted to this page, it will be handled as normal.


7.

If you'd like to play the role of a hacker, make a fake form that submits different values to the handle_comments.php page to see the results (Figure 10.7).

Figure 10.7. If the wrong form inputs are submitted to this page (presumably through malicious intent), the data will be ignored.


Tips

  • This trick was devised by Chris Shiflett, a PHP security consultant. For similar security-related tips and tricks, check out the PHP Security Consortium at www.phpsec.org.

  • The $_SERVER['HTTP_REFERER'] variable reflects the previous page the user accessed before the current one. Using this variable would seem to be a good way to validate that a form was properly submitted from the correct source. Unfortunately, $_SERVER['HTTP_REFERER'] is not reliable, as some browsers will not support it.




    PHP and MySQL for Dynamic Web Sites. Visual QuickPro Guide
    PHP and MySQL for Dynamic Web Sites: Visual QuickPro Guide (2nd Edition)
    ISBN: 0321336577
    EAN: 2147483647
    Year: 2005
    Pages: 166
    Authors: Larry Ullman

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net