2012-07-05 144 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 
} 


// ... 
?>