Issues with spammed php form with javascript validation

Hi, Im not bad with php but seem to be confuzzled with javascript.
I have an index page with built in php form, it apparently uses my process.php page to send the email and a javascript to validate it in an ajax style manner.
I included a hidden honeypot field called city on the form but it never seems to get triggered and am left with receiving lots of spam email thru the form, hoping someone may shed some light.

I have the following form on the index page

<div id="contact-form" class="clearfix">
        <div id="redbg"><h1>Get In Touch!</h1></div>
        <div id="greybg">
		<?php
		//init variables
			$cf = array();
			$sr = false;
			
		if(isset($_SESSION['cf_returndata'])){
				$cf = $_SESSION['cf_returndata'];
			 	$sr = true;
		}
        ?>
        <ul id="errors" class="<?php echo ($sr && !$cf['form_ok']) ? 'visible' : ''; ?>">
            <li id="info">There were some problems with your form submission:</li>
            <?php 
				if(isset($cf['errors']) && count($cf['errors']) > 0) :
					foreach($cf['errors'] as $error) :
			?>
            <li><?php echo $error; ?></li>
            <?php
				endforeach;
				endif;
			?>
        </ul>
        <p id="success" class="<?php echo ($sr && $cf['form_ok']) ? 'visible' : ''; ?>">Thanks for your message! We will get back to you ASAP!</p>
        <br/>
        <form id+"form" method="post" action="process.php"  class="<?php echo ($sr && $cf['form_ok']) ? 'visible' : ''; ?>" >
            <label for="name">Name: <span class="required">*</span></label>
            <input type="text" id="name" name="name" value="<?php echo ($sr && !$cf['form_ok']) ? $cf['posted_form_data']['name'] : '' ?>" placeholder="Your Name" required />

            <input type="hidden" id="city" name="city" value="<?php echo ($sr && !$cf['form_ok']) ? $cf['posted_form_data']['city'] : '' ?>" />
            
            <label for="email">Email Address: <span class="required">*</span></label>
            <input type="email" id="email" name="email" value="<?php echo ($sr && !$cf['form_ok']) ? $cf['posted_form_data']['email'] : '' ?>" placeholder="johndoe@example.com" required />
                
            <label for="telephone">Telephone: </label>
            <input type="tel" id="telephone" name="telephone" value="<?php echo ($sr && !$cf['form_ok']) ? $cf['posted_form_data']['telephone'] : '' ?>" />
                
            <label for="enquiry">Enquiry: </label>
            <select id="enquiry" name="enquiry">
                <option value="Sales" <?php echo ($sr && !$cf['form_ok'] && $cf['posted_form_data']['enquiry'] == 'Sales') ? "selected='selected'" : '' ?>>Sales</option>
                <option value="Support" <?php echo ($sr && !$cf['form_ok'] && $cf['posted_form_data']['enquiry'] == 'Support') ? "selected='selected'" : '' ?>>Website Support</option>
            </select>
              
            <label for="message">Message: <span class="required">*</span>&nbsp;&nbsp;<div id="charNum">0</div></label>
            <textarea id="message" name="message"  oninput="countChar(this)" placeholder="Your message must be greater than 20 charcters" required data-minlength="20"><?php echo ($sr && !$cf['form_ok']) ? $cf['posted_form_data']['message'] : '' ?></textarea>
                
            <span id="loading"></span>
            <input type="submit" value="SEND MESSAGE" id="submit-button" />
            <p id="req-field-desc"><span class="required">*</span> indicates a required field</p>
        </form>
        <?php unset($_SESSION['cf_returndata']);
		 ?>
        <br/>
 	 	</div> 
    </div>

This is the javascript that validates it

$(function(){

	//set global variables and cache DOM elements for reuse later
	var form = $('#contact-form').find('form'),
		formElements = form.find('input[type!="submit"],textarea'),
		formSubmitButton = form.find('[type="submit"]'),
		errorNotice = $('#errors'),
		successNotice = $('#success'),
		formNotice = $('#form'),
		loading = $('#loading'),
		errorMessages = {
			required: ' is a required field',
			email: 'You have not entered a valid email address for the field: ',
			minlength: ' must be greater than '
		}
	
	//feature detection + polyfills
	formElements.each(function(){

		//if HTML5 input placeholder attribute is not supported
		if(!Modernizr.input.placeholder){
			var placeholderText = this.getAttribute('placeholder');
			if(placeholderText){
				$(this)
					.addClass('placeholder-text')
					.val(placeholderText)
					.bind('focus',function(){
						if(this.value == placeholderText){
							$(this)
								.val('')
								.removeClass('placeholder-text');
						}
					})
					.bind('blur',function(){
						if(this.value == ''){
							$(this)
								.val(placeholderText)
								.addClass('placeholder-text');
						}
					});
			}
		}
		
		//if HTML5 input autofocus attribute is not supported
		if(!Modernizr.input.autofocus){
			if(this.getAttribute('autofocus')) this.focus();
		}
		
	});
	
	//to ensure compatibility with HTML5 forms, we have to validate the form on submit button click event rather than form submit event. 
	//An invalid html5 form element will not trigger a form submit.
	formSubmitButton.bind('click',function(){
		var formok = true,
			errors = [];
			
		formElements.each(function(){
			var name = this.name,
				nameUC = name.ucfirst(),
				value = this.value,
				placeholderText = this.getAttribute('placeholder'),
				type = this.getAttribute('type'), //get type old school way
				isRequired = this.getAttribute('required'),
				minLength = this.getAttribute('data-minlength');
			
			//if HTML5 formfields are supported			
			if( (this.validity) && !this.validity.valid ){
				formok = false;
				
				console.log(this.validity);
				
				//if there is a value missing
				if(this.validity.valueMissing){
					errors.push(nameUC + errorMessages.required);	
				}
				//if this is an email input and it is not valid
				else if(this.validity.typeMismatch && type == 'email'){
					errors.push(errorMessages.email + nameUC);
				}
				
				this.focus(); //safari does not focus element an invalid element
				return false;
			}
			
			//if this is a required element
			if(isRequired){	
				//if HTML5 input required attribute is not supported
				if(!Modernizr.input.required){
					if(value == placeholderText){
						this.focus();
						formok = false;
						errors.push(nameUC + errorMessages.required);
						return false;
					}
				}
			}

			//if HTML5 input email input is not supported
			if(type == 'email'){ 	
				if(!Modernizr.inputtypes.email){ 
					var emailRegEx = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/; 
				 	if( !emailRegEx.test(value) ){	
						this.focus();
						formok = false;
						errors.push(errorMessages.email + nameUC);
						return false;
					}
				}
			}
			
			//check minimum lengths
			if(minLength){
				if( value.length < parseInt(minLength) ){
					this.focus();
					formok = false;
					errors.push(nameUC + errorMessages.minlength + minLength + ' charcters');
					return false;
				}
			}
		});
		
		//if form is not valid
		if(!formok){
			
			//animate required field notice
			$('#req-field-desc')
				.stop()
				.animate({
					marginLeft: '+=' + 5
				},150,function(){
					$(this).animate({
						marginLeft: '-=' + 5
					},150);
				});
			
			//show error message 
			showNotice('error',errors);
			
		}
		//if form is valid
		else {
			loading.show();
			$.ajax({
				url: form.attr('action'),
				type: form.attr('method'),
				data: form.serialize(),
				success: function(){
					showNotice('success');
					form.get(0).reset();
					loading.hide();
				}
			});
		}
		
		return false; //this stops submission of the form and also stops browsers showing default error messages
		
	});

	//other misc functions
	function showNotice(type,data)
	{
		if(type == 'error'){
			successNotice.hide();
			errorNotice.find("li[id!='info']").remove();
			for(x in data){
				errorNotice.append('<li>'+data[x]+'</li>');	
			}
			errorNotice.show();
		}
		else {
			errorNotice.hide();
			successNotice.show();
			formNotice.hide();	
		}
	}
	
	String.prototype.ucfirst = function() {
		return this.charAt(0).toUpperCase() + this.slice(1);
	}
	
});


and this is the process.php page

<?php
date_default_timezone_set('Australia/Adelaide');

function clean_input($input){
return strip_tags(trim($input));
}

if( isset($_POST) ){
	
	//form validation vars
	$formok = true;
	$errors = array();
	
	//sumbission data
	$ipaddress = $_SERVER['REMOTE_ADDR'];
	$date = date('d/m/Y');
	$time = date('H:i:s');
	
	//form data
	$name = clean_input($_POST['name']);
	$city = clean_input($_POST['city']);
	$email = clean_input($_POST['email']);
	$telephone = clean_input($_POST['telephone']);
	$enquiry = clean_input($_POST['enquiry']);
	$message = clean_input($_POST['message']);
	
	//validate form data
	if(!empty($city)){
	//redirect back to form
		header('location: ' . $_SERVER['HTTP_REFERER']);
		exit;
	}
	
	//validate name is not empty
	if(empty($name)){
		$formok = false;
		$errors[] .= "You have not entered a name";
	}
	
	//validate email address is not empty
	if(empty($email)){
		$formok = false;
		$errors[] .= "You have not entered an email address";
	//validate email address is valid
	}elseif(!filter_var($email, FILTER_VALIDATE_EMAIL)){
		$formok = false;
		$errors[] .= "You have not entered a valid email address";
	}
	
	//validate message is not empty
	if(empty($message)){
		$formok = false;
		$errors[] .= "You have not entered a message";
	}
	//validate message is greater than 20 charcters
	elseif(strlen($message) < 20){
		$formok = false;
		$errors[] .= "Your message must be greater than 20 characters";
	}
	
	//send email if all is ok
	if($formok){
		$headers = "From: ****@****.com.au" . "\r\n";
		$headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
		
		$emailbody = "<p>You have recieved a new message from the enquiries form on your website.</p>
					  <p><strong>Name: </strong> {$name} </p>
					  <p><strong>Email Address: </strong> {$email} </p>
					  <p><strong>Telephone: </strong> {$telephone} </p>
					  <p><strong>Enquiry: </strong> {$enquiry} </p>
					  <p><strong>Message: </strong> {$message} </p>
					  <p>This message was sent from the IP Address: {$ipaddress} on {$date} at {$time}</p>";
		
		mail("****@****.com.au","New Enquiry",$emailbody,$headers);
		
	}
	
	//what we need to return back to our form
	$returndata = array(
		'posted_form_data' => array(
			'name' => $name,
			'email' => $email,
			'telephone' => $telephone,
			'enquiry' => $enquiry,
			'message' => $message
		),
		'form_ok' => $formok,
		'errors' => $errors,
	);
		
	
	//if this is not an ajax request
	if(empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) !== 'xmlhttprequest'){
		//set session variables
		session_start();
		$_SESSION['cf_returndata'] = $returndata;
		
		//redirect back to form
		header('location: ' . $_SERVER['HTTP_REFERER']);
	}
}

If anyone could shed some light on this would be appreciated

Hello!

The form is working as expected (I tested it). You should log the value whenever your form is sent, that way you would know how many attackers already understand how to bypass your filter.

Why it doesn’t work?

Since you require a hidden input, attackers could leave hidden values alone and bypass the filter. You could use JavaScript to hide the input and/or CSS, so it’s not visible to the users but it’s visible to the attackers, which would fill every field. Then again, the problem still exists: the attacker could simply filter fields that are hidden by either checking the display or visibility property of the tag.

Alternative

Unfortunately, the most effective SPAM prevention is a captcha. Google’s reCAPTCHA is easy to implement.

Hope it helps :slight_smile:,

Regards!

Hi skaparate

Thank you for looking at this for me, I was trying to avoid the captcha path for my users sake, but it appears it is the way to go and yes recaptcha is the better alternative.
I do like your idea of logging how much the hidden form field gets used in comparison to how much spam actually gets thru.
I appreciate you taking the time to help :slight_smile:

2 Likes

Online survey for money have requirements that determine eligibility, usually based on demographic information and your personal habits, so you don’t want to waste time on a website that isn’t going to pan out.