Simple PHP math captcha to minimize bots sending you junky emails?

Eric wrote:

Very interested. Could we do 5 different random math problems?

Though you probably could do it, you don’t really need to do it.

Look back through the first few pages of this thread and you’ll see that the spambots don’t read or analyze your Contact Form - they just harvest your Contact address or form processing URL and send their own data to it. So hiding the address or URL in a PHP script is the first line of defense, and including one simple bot filter (like 6+6) is the second line of defense. As I wrote above, it works great!

The other Contact Form issue I have had is getting lots of junk messages, usually from places offering bad SEO services. My understanding is they use humans and some sort of auto-complete assistance to quickly fill in Contact Forms. Turning off auto-complete on your forms and naming your fields something besides “name”, “email”, “message”, etc is supposed to make their systems not work properly and they will not bother to stop and think and type info into your form. Again, so far it is working wonderfully for me.

One possible future improvement would be some sort of filter for the message field so that if it contains a word you don’t want to get messages about (SEO, viagra?) the submission is rejected. But at the moment, that does not seem necessary. :wink:

@Greg
I’m glad you got everything working. Nice one!
Thanks for taking the time to report back!

@Eric
As Greg says, I’m not sure how effective this would be, but this script does what you want:

myForm.php

<?php $num = rand(1, 5); ?>
<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Spam filter example</title>
  </head>
  <body>
    <form action="myScript.php" method="post">
      <div>
        <label for="spam_question">What is <?php echo $num ?> + <?php echo $num ?>?</label>
        <input type="text" id="spam_question" name="spam_question">
        <input type="hidden" value="<?php echo $num ?>" name="num">
      </div>
      <input type="submit">
    </form>
  </body>
</html>

myScript.php

<?php
  $num = $_POST['num'];
  $spam_question = $_POST['spam_question'];
  if($spam_question != 2*$num){
    echo "Wrong answer!";
    exit();
  }
  echo "It's all good brother!";
?>

Hope that helps.

If memory serves me correct, thats not all together true. They can still hijack your form in order to use it to send out thousands of emails.

Awesome thank you! I’ll try this out and post back if I hit any problems.

Eric wrote:

If memory serves me correct, thats not all together true. They can still hijack your form in order to use it to send out thousands of emails.

I had the same concern. What I learned was that “It depends on your webhosting service”

I use Yahoo webhosting, and their system only allows forms on a website to send email to addresses in the same domain. ( a form on mysite.com can only send emails to contact@mysite.com ) So I didn’t have to worry about outgoing-hijacking for the Contact Form design project we did here. We only had to worry about incoming-hijacking.

So you may want to check your web hosting company to see if they already have outgoing-hijacking blocked on their end…

@pullo I’m starting a new thread so I don’t hijack this one.

OK this worked perfect. But now I need to mirror this function on my JS side. In the past when I just had one answer (not random) I did it like this using the bassistance jquery validation plugin. http://bassistance.de/jquery-plugins/jquery-plugin-validation/. The first statement is a custom rule to prevent urls from being submited in the textarea. So what I need is the same type of thing but using the random math question logic. It has to be possible but does anyone know how? I know this is the php forum but you guys are the best. Thanks!


$.validator.methods.equal = function(value, element, param) {
		return value == param;
	};
	$(document).ready(function(){
		$.validator.addMethod("nourl", 
                    function(value, element) {
                         return !/http\\:\\/\\/|www\\.|link\\=|url\\=/.test(value);
                        }, 
                        "No URL's"
      );
		$("form").validate({
				rules: {
					spam: {
						equal: 4
					}
				},
				messages: {
					spam: ""
				}
			});
	});

Hi Eric,

I’m not too sure what you are asking here.
If I remember correctly, you have a PHP script which chooses one sum from a list of five, then displays it on your form for the user to answer.
It then copies the answer into a hidden field so that the script to which the form is submitted can validate the answer.

Are you saying that you want a separate JavaScript function to generate two random numbers and display them as a sum for the user to complete.
Then when the user presses submit, a function should fire that checks the answer to the sum and will only allow submission if the answer is correct?

Off Topic:

I kinda personally like JS validation to be there to help users fill in the correct data in the right place the right way. Hints. Anything more starts to inherit the problems of false-positive-spams that the back-end validation (which we will have, as Eric has) already has the danger of causing. Having users perform side-work to prove on the dirty client side that they are human, only to have the server do it again in the back seems to want to cause humans torment and dismay. Unless you really need to limit actual conversation with the server…

I think Eric will get a good and useful answer if he states the ultimate purpose of the JS too, and what it might need to deal with.

Hey guys:) pretty much what you said pullo. The php you gave me a while back spits out 5 random math questions - 1+1, 2+2, 3+3, 4+4,5+5 and validates their correct answer. As is they can input a wrong number and my js does not catch it. Only my php does. I’m using my php as a fall back of sorts. I would prefer the js catches the wrong or correct answer first and either displays the error or let’s the form submit. In order to do this it has to exactly mirror the php function you provided earlier. You understand? As Stomme said, yes I am using the js to provide the pretty hints and such. If js is turned off, then php catches it and it just goes to the error page saying the generic “you failed to fill in all the correct information or you made a mistake while typing, please use the back button and try again”.

Thanks for your help!

Furthermore, the code I showed above is how to write a custom rule for the validation plugin. And it showes how I used it in the past to only give 1 single correct answer. I instead of course need to rewrite it (rather I need help) so that it communicates with the php, or at least mirrors it’s random nature.

Ok and here is the test page http://www.visibilityinherit.com/projects/formtest.php and here is the php script http://www.visibilityinherit.com/projects/formmail.txt so you can exactly see what I’m trying to do. Thanks!

Hi Eric,

No problem.
I here’s a modified version of my original PHP script.
I have attached a JavaScript onsubmit handler, which will not allow the form to be submitted if the answer to the question is incorrect.
The way this works is that you use JS to check that the hidden value is equal to two times the submitted value.
Simples!

<?php $num = rand(1, 5); ?>

<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Spam filter example 2</title>
  </head>
  <body>
    <form action="myScript.php" method="post" id="myForm">
      <div>
        <label for="spam_question">What is <?php echo $num ?> + <?php echo $num ?>?</label>
        <input type="text" id="spam_question" name="spam_question">
        <input type="hidden" value="<?php echo $num ?>" name="num" id="ans">
      </div>
      <input type="submit">
    </form>
		
    <script>
      var f = document.getElementById('myForm');
      f.onsubmit = function(){
        if (f.spam_question.value != 2*ans.value){
          alert("Incorrect answer!");
          return false;
        }
      }
    </script>
  </body>
</html>

Awesome!!! Can’t wait to test it out! I’ll let you know how I fare. Thanks a lot Pullo :slight_smile:

Although it works, @Pullo; 's method is quite easy to reverse engineer, since the solution to cracking the ‘captcha’ is right there in the javascript.
So if somebody really wanted they could just look at that code and work around it in a matter of minutes.
Better would be to store the required answer server side in a session and then if you want to validate with javascript do a AJAX request to the server to check if the entered answer is correct, which you can easily do with the bassistance validator using the ‘remote’ method.
This keeps asking the same question as long as it isn’t answered correctly. Once answered correctly it will generate a new captcha, thus lowering the chance of replay attacks.

form.php


<?php
session_start();
if (!isset($_SESSION['num1']) && !isset($_SESSION['num2'])) {
  $_SESSION['num1'] = rand(1,5);
  $_SESSION['num2'] = rand(1,5);
}
?>

<!DOCTYPE HTML> 
<html> 
  <head> 
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 
    <title>Spam filter example 2</title> 
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <script src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.10.0/jquery.validate.js"></script>
  </head> 
  <body> 
    <form action="post.php" method="post" id="myForm">
      <div>
        <label for="spam_question">What is <?php echo $_SESSION['num1'] ?> + <?php echo $_SESSION['num2'] ?>?</label>
        <input type="text" id="spam_question" name="spam_question"> 
      </div> 
      <input type="submit"> 
    </form> 
    <script>
      $("#myForm").validate({
        rules: {
          spam_question: {
            required: true,
            remote: {
              url: "validate-captcha.php",
              type: "post",
            }
          }
        }
      });
    </script>
  </body> 
</html>

post.php


<?php
session_start();
if (!isset($_SESSION['num1']) || !isset($_SESSION['num2'])) {
    exit('Incorrect answer');
}
$sum = (int)$_SESSION['num1'] + (int)$_SESSION['num2'];
if (isset($_POST['spam_question']) && (int)$_POST['spam_question'] === $sum) {
    unset($_SESSION['num1'], $_SESSION['num2']);
    exit('Correct answer!');
}
exit('Incorrect answer');

validate-captcha.php


<?php
session_start();
if (!isset($_SESSION['num1']) || !isset($_SESSION['num2'])) {
    exit('"Incorrect answer."');
}
$sum = (int)$_SESSION['num1'] + (int)$_SESSION['num2'];
if (isset($_POST['spam_question']) && (int)$_POST['spam_question'] === $sum) {
    exit('true');
}
exit('"Incorrect answer."');


@ScallioXTX hey thanks a lot for the alternate method! While your solution does seem to be more secure for the reasons mentioned is it necessarily needed? My thought is JS is basically only used to give hints to the user and etc. And the php backend ultimately protects from spambots and such? So my thought is (maybe I’m wrong?) is that js gives the user hints and makes it pretty while php makes it secure. So if thats the case, if a bot can read the JS captcha, can it therefore also read the mirrored php captcha? Maybe I’m totally off base - if so feel free to let me know.

I am in no way opposed to your method if the above statement is untrue. In which case I just need clarification on where to place said code. The post.php and the validation-captcha.php are identical aside from one line. Is that by design I assume? AND do both snippets go in my formmail.php (aka the php that processes the form)?. If so does order matter? Thanks for your thoughts :slight_smile:

The main difference is that in the JS only version the answer is encoded in the page. So all a spam bot needs to do is fetch that answer and post it with the rest of the form. They only need to figure out first it needs to be multiplied by 2, which is quite easy to find out since it says so in the JS.
In my variant the answer is known by PHP and PHP only (although parsing a simple math question is also quite easy for hackers, but that aside). Tje JS still makes it pretty by sending the answer to the server after it’s filled to see if the given answer is correct.

Yes, that is by design.

  • post.php checks if the posted values are correct; this is the part that goes in your formmail.php.
  • validate-captcha.php is called by the bassistance validation plugin to see if the entered value is correct and returns either an error message which the bassistance plugin will then show the user, or the text “true”, indicating to the bassistance plugin the user entered the correct value. No need to put this one in your formmail.php too; this is just a simple stand alone script for the bassistance validation interaction.

This is a fascinating subject and something that I have been wondering throughout this thread: do you think that your average bot is in a position to do this?
Do you have any idea or insight about the level of “intelligence” one could attribute to a bot?
I would have thought that it would have been easier for a bot to eval any maths questions they encountered (which would render the whole process futile anyway).

The average bot is stupid as crap. All they see is some form on a page which they will then fill out by guessing what each field is for. But I can well imagine that some people are scavenging the internet for forms they can abuse, find out how they work, write a script (or rather, customise one they’ve lying around that already fits quite well) and run it, spamming the form without end. If the answer to hacking the captcha is in the JS this is of course dead simple. Indeed, in this case letting the script fetch the page first, scrape the sum of it, calculate it, and send it with the form isn’t much harder. Indeed, this theory applies more to “real” captchas (ie, random strings) than to simple math. Another option would be to ask questions like “What color is the sky?”, or show an image and ask the user to click the white cat, stuff like that. When the answer is server side, there is nothing the hacker can do but to work out all different questions and answers, which is time consuming so he’ll probably just mosey on to the next form he can get his greasy fingers on.

This is also an interesting read on the subject: http://nedbatchelder.com/text/stopbots.html

Thank you very much for the detailed answer ScallioXTX :slight_smile: I will most likely use your method as it is more secure and therefore better for its intended purpose.

However, my read on what bots are capable of or not tells me that “probably” 99.5% of the time the extra measure is not necessary. I personally don’t think bots are going to scour the code to put all the pieces together to figure out the captcha. In fact, I think they simply click Submit and move on not knowing if it was successful or not. That being today in 2012/2013. However, year 2015-2020 may tell a different story. Bots will inevitably get more sufisticated. In the future they will be able to put the pieces together. Although capable or not, the whole point to the captcha is security so one mine as well go the more secure route.

Thanks a lot guys! With your input this thread is invaluable. :slight_smile: