Jump to content



Photo

Sample Contact Form Script?

php contact form

  • Please log in to reply
3 replies to this topic

#1 Ben

Ben

    Newbie

  • Members
  • Pip
  • 3 posts

Posted 25 January 2012 - 11:04 AM

First, let me say thanks, I've found your PHP book (4th ed.) very useful for a beginner.

In your section on form submission, you give the example of checking a submitted email address in a form, to confirm that it only has one "@" symbol. You mention that, of course, this is only the tip of the iceberg as far as form security.

Now, I have a real-world project where I need a contact form to be fairly hack-resistant, and was wondering if you could point me to a "best practice" PHP script that is up-to-date in terms of validation and security. Something I could study, learn from, etc. There are of course gobs of these posted on various forums, but most are old, oversimple, and seem to lack any real security precautions.

The most useful, complete-seeming contact form script I've come across is here:

http://css-tricks.co...-functionality/

Can you comment/advise/direct on this topic? I know you're not in the business of cookbook-style examples but I need to get this right the first time!
  • 0

#2 Larry

Larry

    Administrator/Writer

  • Administrators
  • 3,841 posts
  • LocationState College, PA (USA)

Posted 25 January 2012 - 11:36 AM

Thanks for the nice words. The CSS link isn't relevant to security, of course. In the security chapter of the book, there are specific recommendations for making contact forms secure. For example, you can use the Filter extension to validate the provided email address.
  • 0

#3 Ben

Ben

    Newbie

  • Members
  • Pip
  • 3 posts

Posted 29 January 2012 - 3:09 PM

Thanks Larry. I know the site is called "css-tricks" but it's not all in the title! Don't judge a site by its URL? B)

The linked page is actually a PHP script with a host of form validation techniques. I understand the Filter extension you're referring to. That's great for a beginner to understand. I guess I was wondering if that's enough in the real world, in a security conscious production environment in the year 2012?

Here, below, is the PHP snippet from the link that I was referring to. It uses session-specific tokens, regular expressions to "clean" the email address, and writes any bounced efforts to a hacklog. I wanted to avoid dumping the whole snippet here but maybe it's easier to refer to directly:



<?php

	session_start();
	
	function getRealIp() {
	   if (!empty($_SERVER['HTTP_CLIENT_IP'])) {  //check ip from share internet
		 $ip=$_SERVER['HTTP_CLIENT_IP'];
	   } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {  //to check ip is pass from proxy
		 $ip=$_SERVER['HTTP_X_FORWARDED_FOR'];
	   } else {
		 $ip=$_SERVER['REMOTE_ADDR'];
	   }
	   return $ip;
	}

	function writeLog($where) {
	
	 $ip = getRealIp(); // Get the IP from superglobal
	 $host = gethostbyaddr($ip);	// Try to locate the host of the attack
	 $date = date("d M Y");
	
	 // create a logging message with php heredoc syntax
	 $logging = <<<LOG
	 \n
	 << Start of Message >>
	 There was a hacking attempt on your form. \n
	 Date of Attack: {$date}
	 IP-Adress: {$ip} \n
	 Host of Attacker: {$host}
	 Point of Attack: {$where}
	 << End of Message >>
LOG;
// Awkward but LOG must be flush left
	
			// open log file
	 if($handle = fopen('hacklog.log', 'a')) {
	
	 fputs($handle, $logging);  // write the Data to file
	 fclose($handle);		   // close the file
	
	 } else {  // if first method is not working, for example because of wrong file permissions, email the data
	
	 $to = 'ADMIN@gmail.com';  
			 $subject = 'HACK ATTEMPT';
			 $header = 'From: ADMIN@gmail.com';
			 if (mail($to, $subject, $logging, $header)) {
			 echo "Sent notice to admin.";
			 }
	
	 }
	}

	function verifyFormToken($form) {
		
		// check if a session is started and a token is transmitted, if not return an error
	 if(!isset($_SESSION[$form.'_token'])) {
	 return false;
		}
	
	 // check if the form is sent with token in it
	 if(!isset($_POST['token'])) {
	 return false;
		}
	
	 // compare the tokens against each other if they are still the same
	 if ($_SESSION[$form.'_token'] !== $_POST['token']) {
	 return false;
		}
	
	 return true;
	}
	
	function generateFormToken($form) {
	
		// generate a token from an unique value, took from microtime, you can also use salt-values, other crypting methods...
	 $token = md5(uniqid(microtime(), true));  
	
	 // Write the generated token to the session variable to check it against the hidden field when the form is sent
	 $_SESSION[$form.'_token'] = $token;
	
	 return $token;
	}
	
	// VERIFY LEGITIMACY OF TOKEN
	if (verifyFormToken('form1')) {
	
		// CHECK TO SEE IF THIS IS A MAIL POST
		if (isset($_POST['URL-main'])) {
		
			// Building a whitelist array with keys which will send through the form, no others would be accepted later on
			$whitelist = array('token','req-name','req-email','typeOfChange','urgency','URL-main','addURLS', 'curText', 'newText', 'save-stuff', 'mult');
			
			// Building an array with the $_POST-superglobal
			foreach ($_POST as $key=>$item) {
					
					// Check if the value $key (fieldname from $_POST) can be found in the whitelisting array, if not, die with a short message to the hacker
			 if (!in_array($key, $whitelist)) {
			
			 writeLog('Unknown form fields');
			 die("Hack-Attempt detected. Please use only the fields in the form");
			
			 }
			}
			
			
			
			
			
			
			// Lets check the URL whether it's a real URL or not. if not, stop the script
			
			if(!filter_var($_POST['URL-main'],FILTER_VALIDATE_URL)) {
			 writeLog('URL Validation');
			 die('Hack-Attempt detected. Please insert a valid URL');
			}
	
	
	
	
	
			// SAVE INFO AS COOKIE, if user wants name and email saved
			
			$saveCheck = $_POST['save-stuff'];
			if ($saveCheck == 'on') {
				setcookie("WRCF-Name", $_POST['req-name'], time()+60*60*24*365);
				setcookie("WRCF-Email", $_POST['req-email'], time()+60*60*24*365);
			}
			
			
			
			
			// PREPARE THE BODY OF THE MESSAGE

$message = '<html><body>';
$message .= '<img src="http://css-tricks.com/examples/WebsiteChangeRequestForm/images/wcrf-header.png" alt="Website Change Request" />';
$message .= '<table rules="all" style="border-color: #666;" cellpadding="10">';
$message .= "<tr style='background: #eee;'><td><strong>Name:</strong> </td><td>" . strip_tags($_POST['req-name']) . "</td></tr>";
$message .= "<tr><td><strong>Email:</strong> </td><td>" . strip_tags($_POST['req-email']) . "</td></tr>";
$message .= "<tr><td><strong>Type of Change:</strong> </td><td>" . strip_tags($_POST['typeOfChange']) . "</td></tr>";
$message .= "<tr><td><strong>Urgency:</strong> </td><td>" . strip_tags($_POST['urgency']) . "</td></tr>";
$message .= "<tr><td><strong>URL To Change (main):</strong> </td><td>" . $_POST['URL-main'] . "</td></tr>";
$addURLS = $_POST['addURLS'];
if (($addURLS) != '') {
   $message .= "<tr><td><strong>URL To Change (additional):</strong> </td><td>" . strip_tags($addURLS) . "</td></tr>";
}
$curText = htmlentities($_POST['curText']);		  
if (($curText) != '') {
   $message .= "<tr><td><strong>CURRENT Content:</strong> </td><td>" . $curText . "</td></tr>";
}
$message .= "<tr><td><strong>NEW Content:</strong> </td><td>" . htmlentities($_POST['newText']) . "</td></tr>";
$message .= "</table>";
$message .= "</body></html>";




//  MAKE SURE THE "FROM" EMAIL ADDRESS DOESN'T HAVE ANY NASTY STUFF IN IT

$pattern = "/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$/i";
			if (preg_match($pattern, trim(strip_tags($_POST['req-email'])))) {
				$cleanedFrom = trim(strip_tags($_POST['req-email']));
			} else {
				return "The email address you entered was invalid. Please try again!";
			}


			
			
			//   CHANGE THE BELOW VARIABLES TO YOUR NEEDS
			
$to = 'JUNKKKKK@gmail.com';

$subject = 'Website Change Reqest';

$headers = "From: " . $cleanedFrom . "\r\n";
$headers .= "Reply-To: ". strip_tags($_POST['req-email']) . "\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: text/html; charset=ISO-8859-1\r\n";

			if (mail($to, $subject, $message, $headers)) {
			  echo 'Your message has been sent.';
			} else {
			  echo 'There was a problem sending the email.';
			}
			
			// DON'T BOTHER CONTINUING TO THE HTML...
			die();
		
		}
	} else {
	
	if (!isset($_SESSION[$form.'_token'])) {
	
	} else {
	echo "Hack-Attempt detected. Got ya!.";
	writeLog('Formtoken');
		}
  
	}

?>

<!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="fr" lang="fr">

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<title>Website Change Request Form</title>

<link rel="stylesheet" href="css/jqtransform.css" type="text/css" media="all" />
<link rel="stylesheet" href="css/style.css" type="text/css" media="all" />

<script src="http://www.google.com/jsapi" type="text/javascript"></script>
	<script type="text/javascript">
		google.load("jquery", "1.3.2");
	</script>
	
<script type="text/javascript" src="js/jquery.jqtransform.js"></script>
<script type="text/javascript" src="js/jquery.validate.js"></script>
<script type="text/javascript" src="js/jquery.form.js"></script>

<script type="text/javascript" src="js/websitechange.js"></script>

</head>

<?php
   // generate a new token for the $_SESSION superglobal and put them in a hidden field
$newToken = generateFormToken('form1');  
?>

<body>

	<div id="page-wrap">
	
	<h1>Website Change Request Form</h1>

<form action="index.php" method="post" id="change-form">

   <input type="hidden" name="token" value="<?php echo $newToken; ?>">

<div class="rowElem">
			<label for="req-name">Your Name*:</label>
			<input type="text" id="req-name" name="req-name" class="required" minlength="2" value="<?php echo $_COOKIE["WRCF-Name"]; ?>" />
		</div>
		
		<div class="rowElem">
			<label for="req-email">Your Email:</label>
			<input type="text" name="req-email" class="required email" value="<?php echo $_COOKIE["WRCF-Email"]; ?>" />
		</div>
		
		<div class="rowElem">
   <label>Type of Change:</label>
  
   <div id="changeTypeArea">

	 <input type="radio" name="typeOfChange" id="existing" value="Change to Existing Content" checked="checked" />
	 <label for="existing">Change to Existing Content</label>
	
	 <div class="clear"></div>
	
	 <input type="radio" id="add-new" name="typeOfChange" value="Add New Content" />
	 <label for="add-new">Add New Content</label>

</div>
		</div>
		
		<div class="rowElemSelect">
<label for="urgency">How Urgent:</label>
<select name="urgency">
<option value="Super Wicked Urgent">Super Wicked Urgent</option>
<option value="ASAP">ASAP</option>
<option value="When you get to it">When you get to it</option>
<option value="It can wait">It can wait</option>
</select>
</div>
		
		<div class="rowElem">
			<label for="URL-main">URL of Page:</label>
			<input type="text" name="URL-main" class="required url" />
		</div>
		
<div class="rowElem">
<label for="mult">Change on multiple pages?</label>
<input type="checkbox" name="mult" id="multCheck" />
		</div>
		
		<div id="addURLSArea">
			<div class="rowElem">
	   <label for="addURLs">Additional URL's / Areas:</label>
	   <textarea cols="40" rows="4" name="addURLS"></textarea>
			</div>
		</div>
		
		<div id="curTextArea">
	 <div class="rowElem">
	   <label for="curText">CURRENT Text / Content:</label>
	   <textarea cols="40" rows="8" name="curText"></textarea>
			</div>
		</div>

<div class="rowElem" id="newTextArea">
<label for="newText">NEW Text / Content:</label>
<textarea cols="40" rows="8" name="newText" class="required" minlength="2"></textarea>
		</div>

<div class="rowElem">
<label> &nbsp; </label>
<input type="submit" value="Send Request!" />
		</div>
		
		<div class="rowElem">
<label> &nbsp; </label>
<input type="checkbox" name="save-stuff" />
<label for="save-stuff">&nbsp; Save Name and Email?</label>
		</div>
		
</form>

</div>
</script>
</body>
</html>

  • 0

#4 Larry

Larry

    Administrator/Writer

  • Administrators
  • 3,841 posts
  • LocationState College, PA (USA)

Posted 30 January 2012 - 10:07 PM

I have no problems with using Filter for the email address validation. Then I run all the data to be sent in the email through a cleaning function. And that's sufficient, in my opinion. Logging is a reasonable idea, but you'd have to watch the logs and then know what you'd do should you see something in the logs.
  • 0