1

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

但是,偶尔有一个稀疏的用户告诉我,当数字正确时,系统不接受他的验证码(错误代码)。每次我都在检查他们的 cookie 设置、清除缓存等,但在这些情况下似乎没有任何效果。

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

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();在表单页面和表单结果页面上调用。

如果有人可以查明此脚本中的潜在错误原因,我将不胜感激!

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


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


按照Ugo Méda的说法,我一直在做一些实验。这是我创建的(为方便起见进行了简化):

表格

// 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脚本的参数来简化过程吗?

如果我们这样做(按照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)再次解密表单提交上的数字以与用户输入进行比较 $decrypted = rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($key), base64_decode($encrypted), MCRYPT_MODE_CBC, md5(md5($key))), "\0 ");
if($_POST['norobot'] != $decrypted) { echo '验证码错误!'; 出口; }

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

有人对此有何评论吗?

4

2 回答 2

3

不要只依赖 SESSION 值,原因有两个:

  • 您的会话可能会过期,因此在某些情况下它不起作用
  • 如果用户打开具有相同页面的另一个选项卡,您将有一个奇怪的行为

使用某种令牌:

  • 当您输出表单时生成一个随机 ID,将其与预期的数字(以及当前日期/时间)一起放入您的数据库中
  • 使用此 ID 生成您的图像
  • 使用 ID 在表单中添加隐藏输入
  • 收到 POST 后,从数据库中获取预期值并进行比较
  • 删除此令牌和所有旧令牌(WHERE token == %token AND datetime < DATE_SUB(NOW(), INTERVAL 1 HOUR)例如)
于 2012-07-05T10:06:20.953 回答
1

It sometimes happens that some visitors can be behind proxies or there is a plugin/software on their computer that can do double-request of some of the files. I have discovered this while developing a project of mine and had some Chrome plugin I have completely forgotten about.

As it is happening to so few of your visitors, it is possible that this is the case. Here are the steps I followed to debug the problem (keep in mind that this was a development environment and I was able to modify the code directly on the site):

When a visitor reports the problem, enable 'debugging' for them which means that I would add their IP to a debug array in the config of the captcha generator. This would do the following:

  1. Acquire the generation time of the image in microtime format.
  2. Write in a log file somewhere on the filesystem every request to the captcha page in a format similar to: ip|microtime|random_numbers
  3. Check the logs for the requests made by the user's IP address and see if there are any close requests in the ranges of about 10 seconds of each other. If there are, then there is something that is making a second request to your captcha page and it is generating a new code, which the visitor cannot see.

Also you need to make sure that after clearing the user's cache, the user is seeing different numbers at every refresh of the page. There can be a quirky behavior on the browser's end and it can be showing an old cached copy nevertheless (seen it on Firefox, you have to clear the cache, restart the browser, clear the cache again and then it works fine).

If this is the case you can do a simple time based addition to your script that does the following:

When generating a new captcha image, check if there is already a captcha numbers set in the session. If they are set, check what time they were generated and if it is less than let's say 10 seconds, just show the same numbers. If it is more than 10 seconds, show new numbers. The only caveat of this method is that you must unset the captcha variable in the session every time you use it.

An example code would be:

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


// ...
?>
于 2012-07-05T10:13:34.940 回答