简体   繁体   中英

Issue with Captcha system on PHP, AJAX Contact form

In 2011, I bought a PHP contact form from codecanyon that uses AJAX to process the form. After reporting my problem to them today, they responded saying that they no longer offer support for their 'old' product (so much for the life time support they generally offer as a rule) ... so they aren't going to help me hence this post on SO.

I would say that this isn't a normal issue but I think it's very important that it gets sorted out - here it is (this is my email to the seller but does explain the problem):

=================

I have an unusual issue with your AJAX Contact Form (you're going to have to read carefully and slowly).

Okay everything works 100% fine BUT ... let me explain (basically this has everything to do with the Captcha and verification of it)

My website has many pages with your online form on each of those pages. Now I also have a listings page that has links going to all of those pages with forms.

EXAMPLE:

Lets say I am on a listings page (a page with a whole load of links going to other pages) and I right click on Link A to open page A in a new tab ... and then I also right click on Link B to open page B in a new tab. Right, so we have the listings page (that's still opened in front of me) and those 2 other pages that opened up in new tabs (Page A and Page B) ... as explained above, both those pages has your online form.

Now, I fill in both forms and click submit.

The first page that I right clicked to open in a new tab (Page A) - that form's Captcha doesn't work even when I've verified it correctly... however the form's Captcha on Page B does work (like it should). Why is it that the Captcha on Page A (the first page I opened) doesn't work?

I get the feeling that in the whole verification system, because Page B was opened up last , the verification is taking that page's captcha code into account, using that captcha for verification (throughout the session surfing on my website) thus making the Captcha on the first opened page (Page A) to not work.

So what I did as an experiment:

I restarted and did the same thing again, IE: I right clicked Link A to open page A in a new tab ... and then I also right click on Link B to open page B in a new tab.

I filled in Page B's Captcha code in Page A's Captcha verification field and what do you know - there's a match!

So this is my problem because I know when some people surf internet (I do this all the time and maybe you do too), they like to right click links to open them in new tabs so that they can get back to them later after browsing the listings page. So the person may have 6 tabs open in the browser and each of those pages has your online form. If the user wants to submit each of those forms, then he/she will experience the exact problem I am reporting above. They will be able to send through 1 form (the last page that was opened in a new tab) but the other page's Captchas won't work unless they refresh the page ... but most people won't think to do that - instead, they will think somethings wrong with the my website - which I am afraid of.

Is there a solution to this? I'm not even sure if you've noticed this before?

I hoped I've explained the situation clearly and I'd really appreciate it if you could assist.

=================

Now back to you. What's causing this?

There are 3 files needed for the form to work / process etc (I'm not including the CSS file in this post not the html for the form as I don't think it's necessary).

1) process.php

2) image.php (this is for the captcha)

3) ajax.js

PROCESS.PHP

<?php if (!isset($_SESSION)) session_start();

if(!$_POST) exit;

if (!defined("PHP_EOL")) define("PHP_EOL", "\r\n");

$address = "email@example.com";
$bcc = "email@example.com";

$name    = $_POST['name'];
$email  = $_POST['email'];
$phone  = $_POST['phone'];
$comments = $_POST['comments'];

if (isset($_POST['verify'])) :
    $posted_verify   = $_POST['verify'];
    $posted_verify   = md5($posted_verify);
else :
    $posted_verify = '';
endif;


$session_verify = $_SESSION['verify'];

if (empty($session_verify)) $session_verify = $_COOKIE['verify'];

$error = '';

    if(trim($name) == '') {
        $error .= '<li>Your name is required.</li>';
    }

    if(trim($email) == '') {
        $error .= '<li>Your e-mail address is required.</li>';
    } elseif(!isEmail($email)) {
        $error .= '<li>You have entered an invalid e-mail  
  address.</li>';
    }

    if(trim($phone) == '') {
        $error .= '<li>Your phone number is required.</li>';
    } elseif(!is_numeric($phone)) {
        $error .= '<li>Your phone number can only contain digits 
 (numbers and no spaces).</li>';
    }

    if(trim($comments) == '') {
        $error .= '<li>You must enter a message to send.</li>';
    }

    if($session_verify != $posted_verify) {
        $error .= '<li>The verification code you entered is 
 incorrect.</li>';
    }

    if($error != '') {
        echo '<div class="error_title"><h6><span>Attention!
</span> Please correct the errors below and try again</h6>';
        echo '<ul class="error_messages">' . $error . '</ul>';
        echo '<div class="close"></div>';
        echo '</div>';

    } else {

    if(get_magic_quotes_gpc()) { $comments = stripslashes($comments); }


     $e_subject = 'Booking / Enquiry';


     $msg = '<html>
<body style="margin:0; padding:0;">

Name: '.$_POST['name'].'
Email: '.$_POST['email'].'
Contact Number:  '.$_POST['phone'].'
Notes: '.$_POST['comments'].'


 </body>
 </html>';



    $msg = wordwrap( $msg, 70 );

    $headers = "From: $email\r\nBCC:{$bcc}\r\n" . PHP_EOL;
    $headers .= "Reply-To: $email" . PHP_EOL;
    $headers .= "MIME-Version: 1.0" . PHP_EOL;
    $headers .= "Content-type: text/html; charset=utf-8" .     PHP_EOL;
    $headers .= 'Content-Transfer-Encoding: 8bit'. "\n\r\n" .  PHP_EOL;

    if(mail($address, $e_subject, $msg, $headers)) {


     echo "<div class='success'>";
     echo "<h6>Your Enquiry has been Successfully submitted.  </h6>";
     echo '<div class="close"></div>';
     echo "</div>";


     } else {

     echo 'ERROR!'; 

     }

   }

    ?>

*Please note that in the process.php code above, I removed a function that seems to validate the email address field - reason why I didn't include it in the code above is because it was heavy with code (would take up a lot of space) and I don't think it's necessary to include

IMAGE.PHP

<?php if (!isset($_SESSION)) session_start(); header("(anti-spam-
content-
type:) image/png");

$enc_num = rand(0, 9999);
$key_num = rand(0, 24);
$hash_string = substr(md5($enc_num), $key_num, 5); // Length of 
String
$hash_md5 = md5($hash_string);

 $_SESSION['verify'] = $hash_md5;


 setcookie("verify", $hash_md5, time()+3600, "/");

 session_write_close();



 $bgs = array("../../img/1.png","../../img/2.png","../../img/3.png");
 $background = array_rand($bgs, 1);


 $img_handle = imagecreatefrompng($bgs[$background]);
 $text_colour = imagecolorallocate($img_handle, 108, 127, 6);
 $font_size = 5;

 $size_array = getimagesize($bgs[$background]);
 $img_w = $size_array[0];
 $img_h = $size_array[1];

 $horiz = round(($img_w/2)-
 ((strlen($hash_string)*imagefontwidth(5))/2), 
 1);
  $vert = round(($img_h/2)-(imagefontheight($font_size)/2));



 imagestring($img_handle, $font_size, $horiz, $vert, $hash_string, 
 $text_colour);
 imagepng($img_handle);


 imagedestroy($img_handle);

 ?>

AJAX.JS

   jQuery(document).ready(function() {
   $('.advertform').submit(function() {
    var action = $(this).attr('action');
    var form = this;
    $('.submit', this).attr('disabled', 'disabled').after(
          '<div class="loader"></div>').addClass("active");
    $('.message', this).slideUp(750, function() {
        $(this).hide();
        $.post(action, {
            name: $('.name', form).val(),
            email: $('.email', form).val(),
            phone: $('.phone', form).val(),
            comments: $('.comments', form).val(),
            verify: $('.verify', form).val()
        },
        function(data) {
            $('.message', form).html(data);
            $('.message', form).slideDown('slow');
            $('.loader', form).fadeOut('fast', function() {
                $(this).remove();
            });
            $('.submit', 
   form).removeAttr('disabled').removeClass("active");
        });
    });
    return false;
   });

   $('.message').on('click', function(){
    $('.message').slideUp();
   });

   });

Looking at the code above, can anyone spot what could be causing this problem? I'm assuming this can has to do with the javascript?

The comments are correct, the validation is failing on some forms because the session only holds the value of the last captcha generated therefore making captchas open in other tabs invalid because their value in the session was overwritten. Because of this, anyone using the same or similar code has this problem.

You can solve it fairly simply by changing the session to store an array of codes instead of just one.

In image.php , change:

$_SESSION['verify'] = $hash_md5;

to:

if (!isset($_SESSION['verify'])) $_SESSION['verify'] = array();
$_SESSION['verify'][$hash_md5] = $hash_md5;  // *explantion for this array key later

You can also get rid of the cookie that gets set for the captcha, session storage should be fine.

Then in your form processor, change:

if($session_verify != $posted_verify) {
    $error .= '<li>The verification code you entered is incorrect.</li>';
}

to:

if(!array_key_exists($posted_verify, $session_verify)) {
    $error .= '<li>The verification code you entered is incorrect.</li>';
}

This should allow you to have multiple forms open in multiple tabs and still be able to submit each one without getting the incorrect captcha error.

Also, another issue with this code is that it doesn't unset the session verify value after a successful post. This means a person could solve one captcha and submit your form an unlimited number of times re-using the old code as long as they don't access image.php again between submissions.

To fix this with the array version, you'll need to unset the session key after the captcha and form is processed.

unset($_SESSION['verify'][$posted_verify]); // remove code from session so it can't be reused

Hope that helps.

I have an idea. Store the captcha values in an array, and keep a counter; both stored in SESSION variables.

So in the form you put a hidden input, and set it to the index.

When we check for captcha, we compare $_SESSION['captcha'][$index] to $_POST['captcha'].

Any time you (the client) open a new window; $index is increased. We pass that index to image.php through the url; example src="img.php?index=2"


Here is a concept; minimal code to accomplish this. Open a couple of windows with this page. See what happens

img.php

<?php 
session_start(); 
header("(anti-spam-content-type:) image/png");

$captcha_text = rand(0, 99999);
// we read a "index" from the URL, example: <img src="img.php?index=2">
$index = isset($_GET['index']) ? (int) $_GET['index'] : 0;

if( empty($_SESSION['captcha'])) {
  $_SESSION['captcha'] = array();
}
$_SESSION['captcha'][$index] = $captcha_text;

// @see http://php.net/manual/en/function.imagestring.php , first example
$im = imagecreate(100, 30);
$bg = imagecolorallocate($im, 55, 255, 255);
$textcolor = imagecolorallocate($im, 0, 0, 255);
imagestring($im, 5, 0, 0, $captcha_text, $textcolor);
header('Content-type: image/png');
imagepng($im);
imagedestroy($im);
?>

index.php

<?php
session_start(); 
// we handle the POST
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_SESSION['captcha'])) {
    if ($_SESSION['captcha'][ $_POST['index'] ] == $_POST['captcha']) {
      echo '<h2>correct</h2>';
    }
    else {
      echo '<h2>not correct</h2>';
    }
    echo '<a href="index.php">Back to form</form>';
    // header('location: index.php');
    exit;
}

// normal page, with form
if(isset($_SESSION['captcha_index'])) {// index
  // set a new index  
  $_SESSION['captcha_index']++;
}
else {
  $_SESSION['captcha_index'] = 0;
}
$captcha_index = $_SESSION['captcha_index']; 

echo '
<img src="img.php?index=' . $captcha_index . '">
<form action="" method="post">
  <input name="captcha">
  <input name="index" type="hidden" value="' . $captcha_index . '">
  <input type="submit" value="GO">
</form>
'; 

// we show what's happening.  Obviously you don't want to print this after test phase
$captcha = isset($_SESSION['captcha']) ? $_SESSION['captcha'] : array();
echo '
<br>print_r of $_SESSION[captcha] 
<pre>' . print_r($captcha, true) . '<pre>
';
?>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM