Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to protect a contact form without captcha?

Tags:

forms

php

On my website I have a couple of forms where someone (without registration) can send message to specified, registered user. The form is simple, and I want to keep that way. What is the best way to protect a contact form againts spam and bots if I don't want to use any captcha?

Your message:...
Your e-mail:...
[Send]
like image 438
Lucas Avatar asked Mar 01 '12 12:03

Lucas


2 Answers

You can protect your form with the form keys technique:

  1. When displaying the form to the user, generate a random ID ("form key") just for this single form submission. Store the form key in a session variable. Submit the "form key" in a hidden input field.
  2. On the server side, check the submitted form key against the one stored in the session variable. Immediately invalidate it after use. (delete it from the session)

Without a captcha or something comparable, it is impossible to really protect a form from abuse.

However, using this technique a malicious spammer would have to

  1. request for form via HTTP for each single form submission to obtain a valid unique forkey
  2. parse the DOM tree / HTML code to obtain the form key
  3. submit it in the correct format

This technique also protects your form from multiple accidential submissions.

Here is a code example:

<?php
session_start();
if (isset($_POST['form_submit'])) {
  // do stuff
  if (isset($_POST['formkey']) && isset($_SESSION['formkeys'][$_POST['formkey']])) {
    echo "valid, doing stuff now ... "; flush();
    // delete formkey from session
    unset($_SESSION['formkeys'][$_POST['formkey']]);
    // release session early - after committing the session data is read-only
    session_write_close();
    // do whatever you like with the form data here
    send_contact_mail($_POST);
  }
  else {
    echo "request invalid or timed out.";
  }
}
else {
  // show form with formkey
  $formkey = md5("foo".microtime().rand(1,999999));
  // put current formkey into list of valid form keys for the user session
  if (!is_array($_SESSION['formkeys']) {
    $_SESSION['formkeys'] = array();
  ]
  $_SESSION['formkeys'][$formkey] = now();
?>
<html>
<head><title>form key</title></head>
<body>
  <form method="POST">
    <input type="hidden" name="PHPSESSID" value="<?=session_id()?>">
    <input type="text" name="formkey" 
      value="<?= $formkey ?>">
    <input type="submit" name="form_submit" value="Contact us!">
  </form>
</body>
</html>
<?php } ?>
like image 61
Kaii Avatar answered Sep 30 '22 17:09

Kaii


So the options:

  1. Maximize query/IP
  2. Add security question
  3. Captcha (even if you don't like it)
  4. Sending e-mail to validate it
  5. Submitting data via JavaScript

Details on these and my opinion on them.

  1. It works well to prevent sending many messages, but a few copies of them will still get in. If you think that's affordable, this might work. Note: a spammer can use proxies or dinamic IP, but that might be slow. Perhaps consider not blocking the user but adding a captcha if they send too many e-mails.
  2. What is this exactly? These are questions like "10+1" or "ten plus 1" or "What day is it?". They might work well - if your website will be in only very few languages. Captchas are still better but this works well in case.
  3. You don't like it, but I still say it's the best. Adding one reCAPTCHA isn't that hard but it will prevent 90% of the spammers - or more. But there are 2 problems with this: 1. sometimes human can't read it as well, 2. spammers could use people to solve it for a minimal (like $0.001/captcha) amount and sometimes they do. But that stands for case 2 as well.
  4. Could be good, but if spammers note this, they can generate random email addresses and validate it via SMTP. But they usually go to the easiest target and leave.
  5. Good, spammers can't make bots act like a click, but they can make codes which makes click non-required. But the easiest target rule stays.

In my opinion, the best is solution 3, then solution 2, then solution 1, then solution 4 and 5.

like image 27
axiomer Avatar answered Sep 30 '22 17:09

axiomer