2012-07-05 145 views
1

我已经使用这个脚本很长一段时间,它在99%完美的作品。用户很容易和清楚,我想继续使用它。这个PHP验证码脚本有什么问题?

但是,偶尔一个稀疏的用户告诉我,系统不接受他的验证码(错误的代码),而数字是正确的。每次我浏览他们的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); 

在我的表单中,我将此脚本作为验证码图像的来源。发送表格后,验证码检查是这样的:

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

请注意session_start();被称为表单页面和表单的结果页面上。

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

P.S .:我意识到验证码脚本的缺点。我知道某些机器人仍然可以读出它们。我不希望使用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; 

的验证码脚本

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

connect(); 
// Find the number associated to the captcha id 
$query = 'SELECT number FROM captcha WHERE uid = "'.mysql_real_escape_string($_GET['number']).'" LIMIT 1'; 
$result = mysql_query($query) or trigger_error(__FUNCTION__.'<hr />'.mysql_error().'<hr />'.$query); 
if (mysql_num_rows($result) != 0){   
    while($row = mysql_fetch_assoc($result)){ 
     $number = $row['number']; 
    } 
} 
disconnect(); 

$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); 
imagettftext($im, 30, 0, 10, 40, $grey, $font, $number); 
imagettftext($im, 20, 3, 18, 25, $white, $font, $number); 

// Generate the image from the number retrieved out of database 
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); 

形式

function get_captcha_number($captcha_uid) { 
    $query = 'SELECT number FROM captcha WHERE uid = "'.mysql_real_escape_string($captcha_uid).'" LIMIT 1'; 
    $result = mysql_query($query); 
    if (mysql_num_rows($result) != 0){   
     while($row = mysql_fetch_assoc($result)){ 
      return $row['number']; 
     } 
    } 
    // Here I would later also enter the DELETE QUERY mentioned above... 
} 
if($_POST['norobot'] != get_captcha_number($_POST['captcha_uid'])) { 
    echo 'Captcha error' 
    exit; 
} 

这一结果工作得很好,非常感谢这个解决方案。

但是,我在这里看到一些潜在的缺点。我注意到至少4个查询,并且对于我们正在做的事情感觉有点资源密集。另外,当用户多次重新加载同一页面(只是做一个混蛋)时,数据库会很快填满。当然这一切都会在下一次提交表格时被删除,但是,你能否跟我一起去看看这个可能的选择?

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

我们这样做(以下the encrypt/decrypt instructions of Alix Axel):

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.'" /> 

3)解密的数目(这是经由_GET $)验证码脚本中发送,并且产生从它的图像

$decrypted = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($key), base64_decode($encrypted), MCRYPT_MODE_CBC, md5(md5($key))), "\0"); 

4)解密上形式的数量再次提交来比较用户输入 $解密= RTRIM(mcrypt_decrypt(MCRYPT_RIJNDAEL_256,MD5($键),BASE64_DECODE($加密),MCRYPT_MODE_CBC,MD5(MD5($键))),“\ 0”);
if($ _ POST ['norobot']!= $ decrypted){ echo'验证码错误!'; 退出; }

一致认为,这有点“安全通过默默无闻”,但它似乎提供了一些基本的安全性,并保持相当简单。或者这种加密/解密操作本身是否过于耗费资源?

有没有人对此有任何评论?

+1

确定另一个加工过程不破坏会话? – Leri 2012-07-05 09:45:12

+0

好评。我很确定,因为在99%的情况下,它能够完美地工作(并且它始终是相同的过程)。 – maartenmachiels 2012-07-05 09:46:20

+0

问题是否可以在访客计算机上始终如一地重现问题,即您是否清理饼干和所有事情,它仍然会发生? – bisko 2012-07-05 09:46:58

回答

3

不要只在SESSION值依赖,有两个原因:如果用户打开带有相同的另一个选项卡

  • 您的会话可能会过期,所以不会在某些情况下
  • 工作页面,你就会有一个怪异的行为

使用某种形式的令牌:

  • 生成一个随机ID,当你OU tput的表单,把它放在你的数据库
  • 使用这个ID生成图像
  • 在窗体当您收到加入一个隐藏的输入与ID
  • 预期的数字(和当前的日期/时间)您POST,从数据库中获取的预期值,并进行比较
  • 删除此令牌,所有的旧的令牌(WHERE token == %token AND datetime < DATE_SUB(NOW(), INTERVAL 1 HOUR)例如)
+1

聪明的混蛋:-)。我很清楚,因为你提到的原因,依靠验证会话进行会话不是一个好主意。我会尽量使用你的建议来创建一个合适的解决方案,然后马上回复你! – maartenmachiels 2012-07-05 10:12:43

+0

嗨,我已经更新了我的答案,并提出了一些您的建议。另外,我提出了一个可能的选择,我希望你的观点。 – maartenmachiels 2012-07-08 17:28:50

+0

谢谢你回答我的问题。我现在有一个解决方案的工作版本。不过,你可以看看我最后一个关于其他版本的评论吗? – maartenmachiels 2012-07-10 08:42:17

1

有时会发生一些游客可以代理的背后还是有一个插件/他们的电脑上的软件可以做一些双重请求 文件。我在开发一个我的项目时发现了这个,并且有一些我完全忘记的Chrome插件。

由于发生在您访问者数量很少的情况,可能是这种情况。以下是我遵循的调试问题的步骤(请记住,这是一个开发环境,我可以直接在网站上修改代码):

当访问者报告问题时,启用'debugging'为这意味着我会将他们的IP添加到验证码生成器配置中的调试数组中。这将执行以下操作:

  1. 以microtime格式获取图像的生成时间。
  2. 在文件系统上的某个日志文件中写入每个对验证码页面的请求,其格式类似于:ip | microtime | random_numbers
  3. 检查用户IP地址发出的请求的日志,看看是否有任何在彼此约10秒的范围内关闭请求。如果有的话,那么有一件事正在向验证码页面发出第二个请求,并且它正在生成一个访问者无法看到的新代码。

此外,您需要确保清除用户的缓存后,用户在每次刷新页面时都会看到不同的数字。浏览器端可能会出现一个古怪的行为,但它可以显示旧的缓存副本(在Firefox上看到它,您必须清除缓存,重新启动浏览器,再次清除缓存,然后才能正常工作)。

如果这是你可以做一个简单的基于时间的除了你的脚本,做如下的情况:

当生成一个新的验证码图像,检查是否已经有在会话中设置验证码的数字。如果它们被设置,请检查它们是什么时间生成的,如果它小于10秒,则只显示相同的数字。如果超过10秒钟,请显示新号码。这种方法唯一的警告是你必须在每次使用时在session中取消设置captcha变量。

一个例子代码如下:

<?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 
} 


// ... 
?>