这个PHP验证码脚本有什么问题?

这个PHP验证码脚本有什么问题?,php,session,captcha,Php,Session,Captcha,我已经使用这个脚本很长时间了,它在99%的情况下工作得非常完美。它对用户来说简单明了,我想继续使用它 然而,偶尔有一位稀疏用户告诉我,当数字正确时,系统不接受他的验证码(错误代码)。每次我都会检查他们的cookies设置、清除缓存等,但在这些情况下,似乎什么都不起作用 因此,我的问题是,在这个脚本的代码中是否有任何原因可以解释异常情况下的故障 session_start(); $randomnr = rand(1000, 9999); $_SESSION['randomnr2'] = md5(

我已经使用这个脚本很长时间了,它在99%的情况下工作得非常完美。它对用户来说简单明了,我想继续使用它

然而,偶尔有一位稀疏用户告诉我,当数字正确时,系统不接受他的验证码(错误代码)。每次我都会检查他们的cookies设置、清除缓存等,但在这些情况下,似乎什么都不起作用

因此,我的问题是,在这个脚本的代码中是否有任何原因可以解释异常情况下的故障

session_start();

$randomnr = rand(1000, 9999);
$_SESSION['randomnr2'] = md5($randomnr);

$im = imagecreatetruecolor(100, 28);
$white = imagecolorallocate($im, 255, 255, 255);
$grey = imagecolorallocate($im, 128, 128, 128);
$black = imagecolorallocate($im, 0,0,0);

imagefilledrectangle($im, 0, 0, 200, 35, $black);

$font = '/img/captcha/font.ttf';

imagettftext($im, 30, 0, 10, 40, $grey, $font, $randomnr);
imagettftext($im, 20, 3, 18, 25, $white, $font, $randomnr);

// Prevent caching
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past3
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");

header ("Content-type: image/gif");

imagegif($im);
imagedestroy($im);
在我的表单中,我将此脚本称为captcha图像的源。发送表单后,通过以下方式检查验证码:

if(md5($_POST['norobot']) != $_SESSION['randomnr2']) {
    echo 'Wrong captcha!';
}
请注意,
session_start()

如果有人能找出这个脚本中潜在的错误原因,我将不胜感激

注:我知道验证码脚本的缺点。我知道某些机器人仍然可以读出它们。我不希望使用Recaptcha,因为它对我的用户来说太难了(不同的语言+很多次的老用户)。我也知道md5很容易解密


编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑编辑


听了乌戈·梅达的话,我一直在做一些实验。这是我创建的(为方便起见简化):

形式

// Insert a random number of four digits into database, along with current time
$query   = 'INSERT INTO captcha (number, created_date, posted) VALUES ("'.rand(1000, 9999).'", NOW(),0)';
$result  = mysql_query($query);

// Retrieve the id of the inserted number
$captcha_uid = mysql_insert_id();

$output .= '<label for="norobot"> Enter spam protection code';
// Send id to captcha script
$output .= '<img src="/img/captcha/captcha.php?number='.$captcha_uid.'" />'; 
// Hidden field with id 
$output .= '<input type="hidden" name="captcha_uid" value="'.$captcha_uid.'" />'; 
$output .= '<input type="text" name="norobot" class="norobot" id="norobot" maxlength="4" required  />';
$output .= '</label>';

echo $output;
这非常有效,因此非常感谢您提供此解决方案

然而,我在这里看到了一些潜在的缺点。我注意到至少有4个问题,对于我们正在做的事情,我感到有点资源密集。此外,当用户多次重新加载同一页面时(只是为了成为一个混蛋),数据库会很快填满。当然,在下一次提交表格时,这些内容都会被删除,但是,您能和我一起检查一下这个可能的替代方案吗

我知道一般不应该加密/解密。然而,由于验证码本质上是有缺陷的(因为机器人的图像读数),我们不能通过加密和解密发送到
captcha.php
脚本的参数来简化过程吗

如果我们这样做(如下所示):

1) 加密一个随机的四位数字符,如下所示:

$key = 'encryption-password-only-present-within-the-application';
$string = rand(1000,9999);
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), $string, MCRYPT_MODE_CBC, md5(md5($key))));
2) 将带有参数的加密数字发送到图像脚本,并将其存储在隐藏字段中

<img src="/img/captcha.php?number="'.$encrypted.'" />
<input type="hidden" name="encrypted_number" value="'.$encrypted.'" />
4) 再次解密表单提交上的数字以与用户输入进行比较 $decrypted=rtrim(mcrypt_decrypt(mcrypt_RIJNDAEL_256,md5($key),base64_decode($encrypted),mcrypt_MODE_CBC,md5(md5($key)),“\0”)
如果($\u POST['norobot']!=$已解密){ 回显“验证码错误!”; 出口 }

同意,这有点“通过模糊实现安全”,但它似乎提供了一些基本的安全性,并且仍然相当简单。或者,这种加密/解密操作本身会占用大量资源吗


有人对此有意见吗?

不要只依赖会话值,原因有二:

  • 您的会话可能会过期,因此在某些情况下无法工作
  • 如果用户打开另一个具有相同页面的选项卡,您将有一个奇怪的行为
使用某种令牌:

  • 在输出表单时生成一个随机ID,并将其与预期数字(以及当前日期/时间)一起放入数据库
  • 使用此ID生成您的图像
  • 在表单中添加ID为的隐藏输入
  • 收到帖子后,从数据库中获取期望值并进行比较
  • 删除此令牌和所有旧令牌(
    WHERE token==%token和
    datetime
    例如)

    • 不要只依赖会话值,原因有两个:

      • 您的会话可能会过期,因此在某些情况下无法工作
      • 如果用户打开另一个具有相同页面的选项卡,您将有一个奇怪的行为
      使用某种令牌:

      • 在输出表单时生成一个随机ID,并将其与预期数字(以及当前日期/时间)一起放入数据库
      • 使用此ID生成您的图像
      • 在表单中添加ID为的隐藏输入
      • 收到帖子后,从数据库中获取期望值并进行比较
      • 删除此令牌和所有旧令牌(
        WHERE token==%token和
        datetime
        例如)

      有时,有些访问者可能在代理之后,或者他们的计算机上有一个插件/软件,可以对某些文件进行双重请求。我在开发我的一个项目时发现了这一点,我完全忘记了一些Chrome插件

      由于这件事发生在你的访客中的人太少了,有可能是这样的。以下是我调试问题所遵循的步骤(请记住,这是一个开发环境,我可以直接在站点上修改代码):

      当访问者报告问题时,为他们启用“调试”,这意味着我会将他们的IP添加到captcha生成器配置中的调试数组中。这将实现以下功能:

    • 以微时间格式获取图像的生成时间
    • 以类似于:ip | microtime | random | u数字的格式将每个对验证码页面的请求写入文件系统某处的日志文件
    • 检查用户IP地址发出的请求的日志,并查看是否存在相互间隔约10秒的关闭请求。如果有,那么有东西正在向您的验证码页面发出第二个请求,它正在生成一个新的代码,而访问者看不到 此外,您还需要确保在清除用户的缓存后,用户在每次刷新页面时都会看到不同的数字。浏览器端可能会有一个奇怪的行为,它可能会显示一个旧的缓存副本(在Firefox上看到),
      $key = 'encryption-password-only-present-within-the-application';
      $string = rand(1000,9999);
      $encrypted = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), $string, MCRYPT_MODE_CBC, md5(md5($key))));
      
      <img src="/img/captcha.php?number="'.$encrypted.'" />
      <input type="hidden" name="encrypted_number" value="'.$encrypted.'" />
      
      $decrypted = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($key), base64_decode($encrypted), MCRYPT_MODE_CBC, md5(md5($key))), "\0"); 
      
      <?php
      
      // begin generating captcha:
      
      session_start();
      
      if (
         empty($_SESSION['randomnr2']) // there is no captcha set
         || empty($_SESSION['randomnr2_time'])  // there is no time set
         || ( time() - $_SESSION['randomnr2_time']  > 10 ) // time is more than 10 secs
      ) {
         $randomnr = rand(1000, 9999);
         $_SESSION['randomnr2'] = md5($randomnr);
         $_SESSION['randomnr2_time'] = microtime(true); // this is the time it was 
                                                        // generated. You can use it 
                                                        // to write in the log file
      }
      
      
      // ...
      ?>