Sanitising variables to avoid XSS (Cross Site Scripting) attacks using PHP

Follow Me on Pinterest

Extending Ian Selby’s RESTful API in PHP – Part 2Once of the biggest considerations when hosting a website is security, this applies to both hosting your own website and also hosting your web site with a hosting company.

One of the biggest headaches for companies is XSS (or Cross Site Scripting) which involves hackers injecting malicious code into form inputs and file uploads. An example of this happening in the hacking of the Sony Playstation network resulting in 100 million users personal data being disclosed.

One of the ways of doing this is encode and escape all your variables before they are sent to your database layer.

Recently I have written a function that streamlines and handles all this processing for the developer.


/**
 * ValidateVariable()
 *
 * method retrieve and sanatize variable requests
 *
 * @author					Neil Young neil.young@neilyoungcv.com
 *
 * EXAMPLE USAGE:
 *
 * $customerId = ValidateVariable('my_variable', 'int', 'request');
 *
 * @param string				$index
 * @param int					$type
 * @param string				$gpcs
 */

function ValidateVariable($index, $type = 'int' , $gpcs = 'request')
{
	//convert the type identifier to uppercase
	$type = strtoupper($type);
	//get the default value
	$value = ($type === 'INT' || $type === 'BOOLEAN') ? 0 : '';	//	default if nothing set
	//GPCS - (Get, Post, Cookie, Server)
	//check if we are looking for a files object otherwise uppercase gpcs
	$gpcs = ($type == 'FILES') ? "FILES" : strtoupper($gpcs);
	//determine which gpcs we are running
	switch($gpcs)
	{
		//GET
		case 'GET':
			//determine if the GCPS is set with the appropriate index
			if(isset($_GET[$index]))
			{
				//determine the value type
				switch($type)
				{
					//integer
					case 'INT':
						//get the value of the integer, making sure we escape to avoid
						//any XSS (Cross Site Scripting) attacks
						$value = (int) htmlentities($_GET[$index], ENT_QUOTES);
						break;
					case 'BOOLEAN':
						//get the value of the boolean, making sure we escape to avoid
						//any XSS (Cross Site Scripting) attacks
						$value = (htmlentities($_GET[$index], ENT_QUOTES)) ? 1 : 0;
						break;
					case 'STRING':
						//get the value of the string, making sure we escape to avoid
						//any XSS (Cross Site Scripting) attacks
						$value = (string) htmlentities($_GET[$index], ENT_QUOTES);
						//trim the string
						$value = trim($value);
						break;
				}

			}

			break;
		//POST
		case 'POST':
			//determine if the GCPS is set with the appropriate index
			if(isset($_POST[$index]))
			{
				//determine the value type
				switch($type)
				{
					//integer
					case 'INT':
						//get the value of the integer, making sure we escape to avoid
						//any XSS (Cross Site Scripting) attacks
						$value = (int) htmlentities($_POST[$index], ENT_QUOTES);
						break;
					case 'BOOLEAN':
						//get the value of the boolean, making sure we escape to avoid
						//any XSS (Cross Site Scripting) attacks
						$value = (htmlentities($_POST[$index], ENT_QUOTES)) ? 1 : 0;
						break;
					case 'STRING':
						//get the value of the string, making sure we escape to avoid
						//any XSS (Cross Site Scripting) attacks
						$value = (string) htmlentities($_POST[$index], ENT_QUOTES);
						//trim the string
						$value = trim($value);
						break;
				}

			}
			break;
		//REQUEST
		case 'REQUEST':
			//determine if the GCPS is set with the appropriate index
			if(isset($_REQUEST[$index]))
			{
				//determine the value type
				switch($type)
				{
					//integer
					case 'INT':
						//get the value of the integer, making sure we escape to avoid
						//any XSS (Cross Site Scripting) attacks
						$value = (int) htmlentities($_REQUEST[$index], ENT_QUOTES);
						break;
					case 'BOOLEAN':
						//get the value of the boolean, making sure we escape to avoid
						//any XSS (Cross Site Scripting) attacks
						$value = (htmlentities($_REQUEST[$index], ENT_QUOTES)) ? 1 : 0;
						break;
					case 'STRING':
						//get the value of the string, making sure we escape to avoid
						//any XSS (Cross Site Scripting) attacks
						$value = (string) htmlentities($_REQUEST[$index], ENT_QUOTES);
						//trim the string
						$value = trim($value);
						break;
				}

			}
			break;
		//COOKIE
		case 'COOKIE':
			//determine if the GCPS is set with the appropriate index
			if(isset($_COOKIE[$index]))
			{
				//determine the value type
				switch($type)
				{
					//integer
					case 'INT':
						//get the value of the integer, making sure we escape to avoid
						//any XSS (Cross Site Scripting) attacks
						$value = (int) htmlentities($_COOKIE[$index], ENT_QUOTES);
						break;
					case 'BOOLEAN':
						//get the value of the boolean, making sure we escape to avoid
						//any XSS (Cross Site Scripting) attacks
						$value = (htmlentities($_COOKIE[$index], ENT_QUOTES)) ? 1 : 0;
						break;
					case 'STRING':
						//get the value of the string, making sure we escape to avoid
						//any XSS (Cross Site Scripting) attacks
						$value = (string) htmlentities($_COOKIE[$index], ENT_QUOTES);
						//trim the string
						$value = trim($value);
						break;
				}

			}
			break;
		//SERVER
		case 'SERVER':
			//determine if the GCPS is set with the appropriate index
			if(isset($_SERVER[$index]))
			{
				//get the value of the server index
				$value = $_SERVER[$index];
			}
			break;
		//SESSION
		case 'SESSION':
			//determine if the GCPS is set with the appropriate index
			if(isset($_SESSION[$index]))
			{
				//determine the value type
				switch($type)
				{
					//integer
					case 'INT':
						//get the value of the integer, making sure we escape to avoid
						//any XSS (Cross Site Scripting) attacks
						$value = (int) htmlentities($_SESSION[$index], ENT_QUOTES);
						break;
					case 'BOOLEAN':
						//get the value of the boolean, making sure we escape to avoid
						//any XSS (Cross Site Scripting) attacks
						$value = (htmlentities($_SESSION[$index], ENT_QUOTES)) ? 1 : 0;
						break;
					case 'STRING':
						//get the value of the string, making sure we escape to avoid
						//any XSS (Cross Site Scripting) attacks
						$value = (string) htmlentities($_SESSION[$index], ENT_QUOTES);
						//trim the string
						$value = trim($value);
						break;
				}

			}
			break;
		//FILES
		case 'FILES':
			//check to see if the $_FILES array is set.
			if(isset($_FILES[$index]))
			{
				//check that the user has provided a maximum file size
				if (!isset($_REQUEST['MAX_FILE_SIZE']))
				{
					//return false as we cannot check the size of the file
					throw new Exception("Please specify a MAX_FILE_SIZE for your upload form");	

				}
				//check that the user has provided a maximum file size
				if (!isset($_REQUEST['ALLOWED_FILE_TYPES']))
				{
					//return false as we cannot check the size of the file
					throw new Exception("Please specify an ALLOWED_FILE_TYPES for your upload form");		

				}
				//get the maximum file size but sanitise the value
				$maxFileSize = htmlentities($_REQUEST['MAX_FILE_SIZE'], ENT_QUOTES);
				$allowedFileTypes = htmlentities($_REQUEST['ALLOWED_FILE_TYPES'], ENT_QUOTES);
				//get the allowed file types
				$validFileTypes = explode(',', $allowedFileTypes);
				//we count how many files have been uploaded
				$fileCount = count($_FILES[$index]['tmp_name']);
				//if we only have one file
				if ($fileCount == 1)
				{
					//first we determine if the file has been uploaded via http post
					if (!is_uploaded_file($_FILES[$index]['tmp_name']))
					{
						//return false as file was not posted
						throw new Exception("A valid file upload was not found");	

					}
					//next we check that the size is what we are expecting
					else if ($_FILES[$index]['size'] >= $maxFileSize)
					{
						//return false as there is a file that is greater than the maximum size
						throw new Exception("The size of your file exceeds the maximum file size of " . round($_FILES[$index]['size'] / $maxFileSize, 2) . " MB");

					}
					//file does not exist
					else if (!file_exists($_FILES[$index]['tmp_name']))
					{
						//the file does not exist so cannot be referenced
						throw new Exception("A file was not found on upload");

					}
					else if (!in_array($_FILES[$index]['type'], $validFileTypes))
					{
						//the file does not exist so cannot be referenced
						throw new Exception("An invalid file type was found, valid file types are " . implode(", ", $validFileTypes));

					}
					else if($_FILES[$index]['error'] != UPLOAD_ERR_OK)
					{
						//there was a problem uploading the image
						throw new Exception("There was an error whilst uploading your file");	

					}

				}
				else
				{
					//initialise counter
					//loop through each file
					for($i = 0; $i <= ($fileCount-1); $i++)
					{

						//first we determine if the file has been uploaded via http post
						if (!is_uploaded_file($_FILES[$index]['tmp_name'][$i]))
						{
							//return false as file was not posted
							throw new Exception("A valid file upload was not found");	

						}

						//next we check that the size is what we are expecting
						if ((int)$_FILES[$index]['size'][$i] > (int)$maxFileSize)
						{
							//return false as there is a file that is greater than the maximum size
							throw new Exception("The size of your file exceeds the maximum file size of " . round($_FILES[$index]['size'] / $maxFileSize, 2) . " MB");

						}
						else if (!file_exists($_FILES[$index]['tmp_name'][$i]))
						{
							//the file does not exist so cannot be referenced
							throw new Exception("A file was not found on upload");

						}
						else if (!in_array($_FILES[$index]['type'][$i], $validFileTypes))
						{
							//the file does not exist so cannot be referenced
							throw new Exception("An invalid file type was found, valid file types are " . implode(", ", $validFileTypes));

						}
						else if($_FILES[$index]['error'][$i] != UPLOAD_ERR_OK)
						{
							//there was a problem uploading the image
							throw new Exception("There was an error whilst uploading your file");	

						}

					}

				}

				//files have been posted and are not larger than the maximum file size
				return $_FILES;	

			}
			break;
		}
		//return the sanitised value
		return $value;
}

Reading through the comments you can see how this works. The function takes the name of the posted variable index, the GPCS (Get, Post, Cookie, Server) request, and what type the function is expecting – int, string etc. From this, we add html entities to our unescaped values and return them back to the variable.

An example would be:

//we cannot use $username = $_POST["username"]; as this would be harmful
//so...
//we use the function above to process and return the variable's value
$username = ValidateVariable('username', 'string', 'post');

For files, we want to ensure that we avoid any resource injections so we process this differently as we only want to ensure that the files have been successfully uploaded and if the file is in fact an uploaded file posted from a safe form. We also insist that two things are posted with the file so we can validate it further – these are ALLOWED_FILE_TYPES which will specify what mime type of files you are willing to allow and the MAX_FILE_SIZE which specifies the largest size of file (in bytes) you are willing to allow to be posted to your server.

Consider the following form:

<form action="test.php" method="POST" enctype="multipart/form-data">
<input type="text" name="my_name" />
<input type="text" name="my_email" />
<input type="file" name="my_file" />
<input type="hidden" name="submitted" value="1" />
<input type="hidden" name="ALLOWED_FILE_TYPES" value="image/gif,image/jpeg,image/png" />
<input type="hidden" name="MAX_FILE_SIZE" value="10485760" />
<input type="submit" />
</form>

The form is very simple, has our entry for a name, an email and allows us to select a file to upload to the server. A number of things can happen that could potentially cause damage to our server if we do not escape our strings and handle our file uploads carefully so we use the function like this.


//determine if the user has submitted the form
//will return 0 if not set
$submitted = ValidateVariable("submitted", "int", "post");

if ($submitted)
{
	//get our form variables and escape them to avoid XSS
	$name = ValidateVariable("my_name", "string", "post");
	$email = ValidateVariable("my_email", "string", "post");

	//attempt to execute our code
	try
	{
		//get the file we want to process
		$file = ValidateVariable("my_file", "files");
		//if the variable $file is deemed a valid file then the $_FILES array will be returned to the developer
		//carry on handling our files and variables.....
	}
	catch(Exception $e)
	{
		//display the error and halt the script
		die($e->getMessage());
	}
}

At the point when your variables and files are deemed safe you can the insert your values for the variables into your database and upload your files with the knowledge that these have been handled at the server level safely.

One last thing that you need to do before the insertion into your database is escape values ready for the database.

This would be achieved with the following:

//escape your strings ready for database insertion
$sql = "INSERT INTO users (name, email) VALUES ('" . mysql_real_escape_string($name) . "', '" . mysql_real_escape_string($email) . "')";

I hope you found this useful :-)

Filed Under: Blog, Code Share, Security
Tags: , , , , , , , , , .
Bookmark: permalink.

Let me know what you think:

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>