Page 1 of 1

Validating and Controlling Form Submissions Rate Topic: -----

#1 codeprada  Icon User is offline

  • Changed Man With Different Priorities
  • member icon

Reputation: 948
  • View blog
  • Posts: 2,357
  • Joined: 15-February 11

Posted 18 October 2011 - 05:59 PM

In this tutorial I will demonstrate how you can control and validate form submissions.

You can find a form on just about every web application that accepts input from a client. This opens up the door to spam and automated requests from malicious users. All it takes is a simple right click and view page source then you've got the name of the script that processes the form data in the action attribute. Automated attacks can be done on that script with cURL or even sending the headers and data manually (fsockopen). Throughout the course of this tutorial I'll be using a simple form that is used to send an email.
<form action="send.php" name="mail" method="post">
    <input type="text" name="name">
    <input type="text" name="email">
    <textarea name="message"></textarea>
    <input type="submit" value="Send" name="submit">
</form>



A typical email feature follows this process:
  • User fills out HTML form and then submits the data.
  • Data is sent either via a GET or POST request.
  • Email script validates the data it receives and decides whether it is valid or not. (i.e. Checks for empty fields, etc)
  • If data is valid then it is sanitized and sent
  • If not then an error is reported back to the user.


We're going to be focusing on a few aspects of Step 3 throughout this tutorial. There are a few questions* you must ask before marking a form submission as valid:
  • Who submitted this data?
  • Has this form been marked as expired?
  • Has the time limit if any passed between form submissions?


*Not limited to the following

Who Submitted This Data?
Not all data that from POST or GET actually came from a legitimate source. We must always remember that send.php is a publicly accessible file so we're challenged with the task of filtering out the request that came only from our form. Before I jump into how we can prevent such I'll give you a brief explanation as to how send.php can be easily exploited.

Our form has four elements so therefore we will assume that send.php requires all four. We also know that it only checks POST for this data. The POST data will therefore look like this.
name=<name here>&email=<email here>&message=<message here>&submit=Send

We've acquired a lot of information from just looking at the HTML of a simple form. We now need a way to send this data to send.php. Here's an example using fsockopen.
<?php
    define('HOST', 'example.com');
    
    $fsock = fsockopen(HOST, 80, $errno, $errstr);
    
    if(is_resource($fsock))
    {
	//our data
        $data = 'name=<name here>&email=<email here>&message=<message here>&submit=Send';

	//the script we're targetting
        $path = '/path/to/send.php';
        
        fputs($fsock, 'POST ' . $path . 'HTTP/1.1' . "\r\n"); //We're POSTing data to $path
        fputs($fsock, 'HOST: ' . HOST . "\r\n"); 
        fputs($fsock, 'Content-Type: text\plain' . "\r\n");
        fputs($fsock, 'Content-Length: ' . strlen($data) . "\r\n");
        fputs($fsock, 'Connection: close' . "\r\n\r\n");
        fputs($fsock, $data);
        fclose($fsock);
    }
?>


To our send.php script the data received from this script will seem valid providing we've added the data with the correct format (name, email , message). How would we check the source of this data? We could check HTTP_REFERER but that can easily be spoofed by sending a Referer ('Referer: http://www.example.com/contact') header. The next solution is to place a unique key within a hidden value in the form.

Placing a unique key within the form will ensure that the user had to have visited our contact page thus there's a huge possibility the request came from there. Let's revise our form.
<form action="send.php" name="mail" method="post">
    <input type="text" name="name">
    <input type="text" name="email">
    <textarea name="message"></textarea>
    <input type="hidden" name="key" value="unique key goes here">
    <input type="submit" value="Send" name="submit">
</form>


How you make the key is completely up to you but it should be as unique and random as possible. Here's a snippet of how I create my keys
<?php
	$characters = array_merge(
		range(0, 9),
		range('a', 'z'),
		range('A', 'Z')
	);
	//should be random enough
	$key = hash_hmac('SHA256', shuffle($characters), time());
?>


Placing it within the value attribute of your key field can be done by simply echoing it there or through a template engine but that's beyond the scope of this tutorial.
We must also place this key when generated within a session variable so that we can compare it with the one being submitted. If these keys match then it is deleted from the session until a new key has been generated.
<?php
    session_start();
    
    if(isset($_SESSION['key'], $_POST['key']) && $_SESSION['key'] === $_POST['key'])
    {
        //valid key
        unset($_SESSION['key']);
    } 
?>


With the two above snippets you should be able to see what we're trying to achieve here.

Has This Form Been Marked As Expired?
We may want to assign an expiry date to each key for added security. To do this we must take a slightly different approach. To create an expiry time we'll simply add the amount of seconds unto the current timestamp of when the key is generated. This must be stored it within the session to be compared with the timestamp of the when the form data is being processed.

Since expiry timestamp and key are related I'd say store it within an array. It can be stored within an object also. It's solely up to you the coder. I'll demonstrate how to do so with an array.

Let's call the session variable to hold our array 'fprotect'. Therefore modifying the script about we'll have something like this.
<?php
	session_start();
	$characters = array_merge(
		range(0, 9),
		range('a', 'z'),
		range('A', 'Z')
	);
	//should be random enough
	$key = hash_hmac('SHA256', shuffle($characters), time());
	$expiry = time() + 3600; //expires in a hour (60 seconds * 60 minutes)
	//array will be serialized automatically
	//depending on your version of PHP this may not be so. Refer to manual
	//if needs be using serialize is necessary here
	$_SESSION['fprotect'] = array('key' => $key, 'expire' => $expiry);
?>


As you see above we now have an expiry date set for each key that is generated. To check whether a key has expired and simple IF statement should suffice.
<?php
    session_start();
    
    if(isset($_SESSION['fprotect'], $_POST['key']) && is_array($_SESSION['fprotect'])  && $_SESSION['fprotect']['key'] === $_POST['key'] && $_SESSION['fprotect']['expire'] < time())
    {
        //valid key
	//change the expiry timestamp to the current timestamp so that
	//any submissions after will be marked as expired
        $_SESSION['fprotect']['expire'] = time();
    } 
?>


Our IF statement has grow quite a bit from that last step. I'd recommend placing the check within a function if needs be. Just to clarify $_SESSION['fprotect'] is an associative array so we're accessing the elements via the indices key and expire.

Has The Time Limit If Any Passed Between Form Submissions?
This is a very important feature when one needs to control the frequency of form submissions. This feature can prevent abusers from resubmitting a form over and over. We must now add yet another and the final element to our 'fprotect' array, 'renew'. This element will hold the amount of seconds that must elapse until a new key can be created. You must also note that this time period is relative to the expiry timestamp.
<?php
    session_start();
    if(!isset($_SESSION['fprotect']['expire'], $_SESSION['fprotect']['renew']) || ($_SESSION['fprotect']['expire'] + $_SESSION['fprotect']['renew']) >= time())
    {
        $characters = array_merge(
            range(0, 9),
            range('a', 'z'),
            range('A', 'Z')
        );
        //should be random enough
        $key = hash_hmac('SHA256', shuffle($characters), time());
        $expiry = time() + 3600; //expires in a hour (60 seconds * 60 minutes)
        $renew = 30; //30 seconds between form submissions
        //array will be serialized automatically
        $_SESSION['fprotect'] = array('key' => $key, 'expire' => $expiry, 'renew' => $renew);
    }
?>


Our semi-lengthy IF statement is saying that if the expire or renew is not set or the timestamp the key expire plus the amount of seconds until it is allowed to be renewed is greater than or equal to the current timestamp then create a new key.

Object - FormProtect
I will leave you with an object I made to handle everything explained here. It also supports multiple key handling.
<?php
	/**
	 * Handles the generation and validation of keys
	 *
	 * @author Chavez Watkins
	 * @datecreated 17th October, 2011
	 */
	class FormProtect
	{    
		/**
		 * Makes sure the session is started
		 *
		 * @return (void)
		 */
		private function sessionInitialize()
		{
			if(!isset($_SESSION))
			{
				session_start();
			}
		}
		
		/**
		 * Initializes a valid key to be used within the form
		 * 
		 * @param (string) $name Name of the key
		 * @param (int) $expire Amount of seconds the key will expire in
		 * @param (int) $renew Amount of seconds till next key can be initialized
		 * @return (mixed) Key to be used in form or NULL if initialization failed
		 */
		static public function initKey($name, $expire = 3600, $renew = 0)
		{
			self::sessionInitialize();
			$key = NULL;
			
			if(is_string($name) && is_int($expire) && is_int($renew))
			{
				$name = md5($name);
				
				//Do not create a new key until the time has passed to renew it			
				if(isset($_SESSION[$name]['renew']) && $_SESSION[$name]['renew'] + $_SESSION[$name]['expire'] >= time())
				{
					$key = $_SESSION[$name]['key'];	
				}
				else
				{
					$characters = array_merge(
						range(0, 9),
						range('a', 'z'),
						range('A', 'Z')
					);
					$key = hash_hmac('SHA256', shuffle($characters), time());
					
					$_SESSION[$name] = array('key' => $key, 'expire' => time() + $expire, 'renew' => $renew);
				}
			}
			
			return $key;
		}
		
		/**
		 * Checks whether a form is valid or not
		 *
		 * @param ($name) The name of the key to be checked
		 * @param ($input) Name of the index to check in $_REQUEST
		 * @return (bool) True if valid false if invalid
		 */
		static public function isValid($name, $input)
		{
			self::sessionInitialize();
			$valid = false;
			
			if(is_string($name) && is_string($input))
			{
				$name = md5($name);
				
				if(isset($_REQUEST[$input], $_SESSION[$name]) && is_array($_SESSION[$name]))
				{
					if($_REQUEST[$input] == $_SESSION[$name]['key'] && time() <= $_SESSION[$name]['expire'])
					{
						$valid = true;
						//This key is marked as expired
						$_SESSION[$name]['expire'] = time(); 
					}
				}
			}
			
			return $valid;
		}
	}
?>



Is This A Good Question/Topic? 2
  • +

Replies To: Validating and Controlling Form Submissions

#2 codeprada  Icon User is offline

  • Changed Man With Different Priorities
  • member icon

Reputation: 948
  • View blog
  • Posts: 2,357
  • Joined: 15-February 11

Posted 18 November 2011 - 12:05 PM

Updated version of the FormProtect class.

Usage
//Initialize Key
$key = FormProtect::initKey('login');

//Checking validation
if(FormProtect::isValid('login', $key))
   //is valid
else
   //not valid



FormProtect.class.php
<?php
    /**
     * Handles the generation and validation of keys
     *
     * @author Chavez Watkins
     * @datecreated 17th October, 2011
     */
    class FormProtect
    {    
        /**
         * Makes sure the session is started
         *
         * @return (void)
         */
        private function sessionInitialize()
        {
            if(!isset($_SESSION))
            {
                session_start();
            }
        }
        
        /**
         * Initializes a valid key to be used within the form
         * 
         * @param (string) $name Name of the key
         * @param (int) $expire Amount of seconds the key will expire in
         * @param (int) $renew Amount of seconds till next key can be initialized
         * @return (mixed) Key to be used in form or NULL if initialization failed
         */
        static public function initKey($name, $expire = 3600, $renew = 0)
        {
            self::sessionInitialize();
            $key = NULL;
            
            if(is_string($name) && is_int($expire) && is_int($renew))
            {
                $name = md5($name);
                
                //Do not create a new key until the time has passed to renew it            
                if(isset($_SESSION[$name]['renew']) && $_SESSION[$name]['renew'] + $_SESSION[$name]['expire'] >= time())
                {
                    //Smarty template engine seems to add a new line character to the key
                    //preg_replace isn't need here but just as a precaution
                    $key = preg_replace('#\W#', '', $_SESSION[$name]['key']); 
                }
                else
                {
                    $characters = array_merge(
                        range(0, 9),
                        range('a', 'z'),
                        range('A', 'Z')
                    );
                    $key = hash_hmac('SHA256', shuffle($characters), time());
                    
                    $_SESSION[$name] = array('key' => $key, 'expire' => time() + $expire, 'renew' => $renew);
                }
            }
            
            return $key;
        }
        
        /**
         * Checks whether a form is valid or not
         *
         * @param ($name) The name of the key to be checked
         * @param ($key) The key being validated
         * @return (bool) True if valid false if invalid
         */
        static public function isValid($name, $key)
        {
            self::sessionInitialize();
            $valid = false;
            if(is_string($name) && is_string($key))
            {
                $name = md5($name);
                
                if(isset($_SESSION[$name]) && is_array($_SESSION[$name]))
                {
                    if($key == $_SESSION[$name]['key'] && time() <= $_SESSION[$name]['expire'])
                    {
                        $valid = true;
                        //This key is marked as expired by setting the time to 2 seconds in the past
                        $_SESSION[$name]['expire'] = time() - 2; 
                    }
                }
            }
            return $valid;
        }
    }
?>


Was This Post Helpful? 0
  • +
  • -

Page 1 of 1