PHP、AJAX 联系表上的验证码系统问题

Issue with Captcha system on PHP, AJAX Contact form

2011 年,我从 codecanyon 购买了一个 PHP 联系表格,使用 AJAX 处理表格。在今天向他们报告我的问题后,他们回答说他们不再为他们的 'old' 产品提供支持(他们通常通常提供终身支持)......所以他们不会帮我这个 post on SO.

我会说这不是一个正常问题,但我认为解决它非常重要 - 在这里(这是我给卖家的电子邮件,但确实解释了问题) :

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

我对你的 AJAX 联系表有一个不寻常的问题(你将不得不慢慢地仔细阅读)。

好的,一切都 100% 正常,但是...让我解释一下(基本上这与验证码及其验证有关)

我的网站有很多页面,每个页面上都有您的在线表格。 现在我还有一个列表页面,其中包含指向所有带有表单的页面的链接。

示例:

假设我在一个列表页面(一个包含大量指向其他页面的链接的页面),我右键单击 Link A 以在新选项卡中打开页面 A ... 并且然后我也右键单击 Link B 以在新选项卡中打开页面 B。是的,所以我们有列表页面(仍然在我面前打开)和在新选项卡中打开的其他 2 个页面(页面 A 和页面 B)...如上所述,这两个页面都有您的在线表格。

现在,我填写两个表格并点击提交。

我右键单击以在新选项卡中打开的第一个页面(页面 A)- 即使我已正确验证,该表单的验证码也不起作用...但是页面 B 上的表单验证码确实有效(就像它应该的那样)。为什么A页(我打开的第一页)的验证码不起作用?

我感觉在整个验证系统中,因为B页面是last打开的,所以验证是在考虑那个页面的验证码,用那个验证码来验证(在我的网站上浏览的整个会话期间)因此使第一个打开的页面(页面 A)上的验证码不起作用。

所以我做了一个实验:

我重新启动并再次做了同样的事情,IE:我右键单击 Link A 在新选项卡中打开页面 A ... 然后我也右键单击 Link B 以在新标签页中打开页面 B。

我在页面 A 的验证码字段填写了页面 B 的验证码,你知道吗 - 有一个匹配项!

所以这是我的问题,因为我知道当有些人上网时(我一直这样做,也许你也这样做),他们喜欢右键单击链接以在新选项卡中打开它们,以便他们可以返回浏览列表页面后稍后再给他们。所以这个人可能在浏览器中打开了 6 个选项卡,并且每个页面都有您的在线表单。如果用户想要提交每个表单,那么 he/she 将遇到我在上面报告的确切问题。他们将能够通过 1 个表单发送(在新选项卡中打开的 last 页面),但其他页面的验证码将无法工作,除非他们刷新页面......但大多数人们不会想那样做 - 相反,他们会认为我的网站有问题 - 我很害怕。

有解决办法吗? 我什至不确定你以前是否注意到这一点?

我希望我已经清楚地解释了情况,如果你能提供帮助,我将不胜感激。

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

现在回到你身边。这是什么原因造成的?

表格工作/处理等需要 3 个文件(我没有在这个 post 中包括 CSS 文件,而不是表格的 html,因为我认为没有必要)。

1) process.php

2) image.php(这是验证码)

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!'; 

     }

   }

    ?>

*请注意,在上面的 process.php 代码中,我删除了一个似乎验证电子邮件地址字段的函数 - 我没有将其包含在上面的代码中的原因是因为它很重代码(会占用很多space),我认为没有必要包含

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();
   });

   });

看看上面的代码,谁能找出导致这个问题的原因?我假设这可能与 javascript?

有关

评论正确,某些表单的验证失败,因为会话仅保存最后生成的验证码的值,因此在其他选项卡中打开的验证码无效,因为它们在会话中的值被覆盖。因此,任何使用相同或相似代码的人都会遇到此问题。

您可以通过更改会话以存储一组代码而不是仅存储一个代码来相当简单地解决它。

image.php中,更改:

$_SESSION['verify'] = $hash_md5;

至:

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

您也可以去掉为验证码设置的 cookie,会话存储应该没问题。

然后在您的表单处理器中,更改:

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

至:

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

这应该允许您在多个选项卡中打开多个表单,并且仍然能够提交每个表单而不会收到不正确的验证码错误。

此外,此代码的另一个问题是它不会在成功 post 后取消设置会话验证值。这意味着一个人可以解决一个验证码并无限次提交您的表单,只要他们在提交之间不再次访问 image.php 即可重新使用旧代码。

要使用数组版本解决此问题,您需要在处理验证码和表单后取消设置会话密钥。

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

希望对您有所帮助。

我有个主意。将验证码值存储在一个数组中,并保留一个计数器;都存储在 SESSION 变量中。

所以在表单中放置一个隐藏的输入,并将其设置为索引。

当我们检查验证码时,我们比较 $_SESSION['captcha'][$index] 和 $_POST['captcha'].

任何时候你(客户端)打开一个新的window; $index 增加。 我们通过 url 将该索引传递给 image.php;示例 src="img.php?index=2"


这是一个概念;完成此操作的最少代码。 使用此页面打开几个 windows。看看会发生什么

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>
';
?>