提示:文章寫完后,目錄可以自動生成,如何生成可參考右邊的幫助文檔
文章目錄
-
目錄
文章目錄
前言
原理詳解
1. 前后端驗證邏輯不一致
2. 驗證碼值保存在客戶端
3. 驗證碼可預測或重復
4. 驗證碼驗證與邏輯解耦
一、處理關卡報錯
二、low級別源碼分析
三、medium級別源碼分析
三、high級別源碼分析
安全點說明:
四、impossible級別源碼分析
總結:Impossible級別的安全設計
前言
提示:這里可以添加本文要記錄的大概內容:
Insecure CAPTCHA 模塊的含義是:CAPTCHA 的驗證機制存在安全漏洞,可以被攻擊者輕松繞過,從而進行自動化攻擊(例如暴力破解登錄密碼)。
原理詳解
在 DVWA 的這個模塊中,存在以下常見的實現漏洞:
1. 前后端驗證邏輯不一致
-
前端頁面展示了驗證碼圖片(如一個 5 位數字),用戶輸入后提交表單。
-
但后端未正確校驗或校驗方式過于簡單,甚至根本未檢查驗證碼輸入是否正確。
-
攻擊者可以直接跳過驗證碼輸入字段,構造 POST 請求攻擊。
2. 驗證碼值保存在客戶端
-
有的實現會把驗證碼值直接保存在 cookie、hidden field 或 session 中,攻擊者可以通過抓包或調試 JS 獲取驗證碼答案。
-
甚至可以通過查看源碼或 response 來獲取驗證碼值。
3. 驗證碼可預測或重復
-
如果驗證碼使用的是偽隨機函數(如
rand()
)生成,而種子固定,攻擊者可預測其值。 -
或者驗證碼值在每次訪問時不更新(比如 10 分鐘內都是同一個),那么攻擊者只需識別一次,就可反復使用。
4. 驗證碼驗證與邏輯解耦
-
某些實現中,驗證碼驗證邏輯在前端處理,而沒有在服務端做真正校驗,攻擊者可直接跳過驗證邏輯。
提示:以下是本篇文章正文內容,下面案例可供參考
一、處理關卡報錯
$_DVWA[ 'recaptcha_public_key' ] ?= '';
$_DVWA[ 'recaptcha_private_key' ] = '';
改為:
$_DVWA[ 'recaptcha_public_key' ] = '6LdK7xITAAzzAAJQTfL7fu6I-0aPl8KHHieAT_yJg';
$_DVWA[ 'recaptcha_private_key' ] = '6LdK7xITAzzAAL_uw9YXVUOPoIHPZLfw2K1n5NVQ';
重啟或者退出dvwa重新登錄,修改成功
二、low級別源碼分析
<?php// 如果提交了表單,并且 step 是 1(第一步:驗證碼驗證)
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {$hide_form = true; // 隱藏第一步的 CAPTCHA 表單// 獲取用戶輸入的新密碼和確認密碼$pass_new = $_POST[ 'password_new' ]; // 新密碼$pass_conf = $_POST[ 'password_conf' ]; // 確認密碼// 驗證 CAPTCHA,使用第三方(Google reCAPTCHA)$resp = recaptcha_check_answer($_DVWA[ 'recaptcha_private_key'], // 使用私鑰$_POST['g-recaptcha-response'] // 用戶填寫的 CAPTCHA 響應);// 如果 CAPTCHA 驗證失敗if( !$resp ) {$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>"; // 提示 CAPTCHA 錯誤$hide_form = false; // 顯示 CAPTCHA 表單return; // 停止執行后續邏輯}else {// CAPTCHA 驗證通過,檢查兩個密碼是否一致if( $pass_new == $pass_conf ) {// 如果一致,顯示第二步確認提交表單echo "<pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre><form action=\"#\" method=\"POST\"><input type=\"hidden\" name=\"step\" value=\"2\" /> <!-- 隱藏字段,表示進入第二步 --><input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" /> <!-- 隱藏字段,帶著密碼 --><input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" /> <!-- 隱藏字段,帶著密碼確認 --><input type=\"submit\" name=\"Change\" value=\"Change\" /> <!-- 提交按鈕 --></form>";}else {$html .= "<pre>Both passwords must match.</pre>"; // 密碼不一致,提示錯誤$hide_form = false; // 顯示 CAPTCHA 表單}}
}// 如果提交了表單,并且 step 是 2(第二步:真正修改密碼)
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {$hide_form = true; // 隱藏 CAPTCHA 表單// 獲取用戶輸入的新密碼和確認密碼$pass_new = $_POST[ 'password_new' ];$pass_conf = $_POST[ 'password_conf' ];// 再次檢查密碼是否一致if( $pass_new == $pass_conf ) {// 對密碼進行 SQL 注入防護$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));$pass_new = md5( $pass_new ); // 對密碼進行 md5 哈希// 構造 SQL 更新語句,更新當前用戶的密碼$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );echo "<pre>Password Changed.</pre>"; // 提示密碼修改成功}else {echo "<pre>Passwords did not match.</pre>"; // 提示密碼不一致$hide_form = false; // 顯示表單}((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); // 關閉數據庫連接
}?>
核心安全問題(即 “Insecure CAPTCHA” 原理)?
<input type="hidden" name="step" value="2" />
攻擊者可以跳過第一個 CAPTCHA 表單,直接構造第二步請求(step=2)提交新密碼,因為 第二步中沒有再次驗證 CAPTCHA 是否通過,也沒有其他身份校驗機制。
所以這里輸入密碼,用bp抓包,將step的值從1直接改為2,即跳過安全驗證,直接改密
抓包結果如下:
在返回的數據包中發現修改成功
三、medium級別源碼分析
<?phpif( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) { // 如果提交了表單并且當前步驟為第1步// Hide the CAPTCHA form$hide_form = true; // 隱藏 CAPTCHA 表單// Get input$pass_new = $_POST[ 'password_new' ]; // 獲取新密碼$pass_conf = $_POST[ 'password_conf' ]; // 獲取確認密碼// Check CAPTCHA from 3rd party$resp = recaptcha_check_answer( // 調用 Google reCAPTCHA 接口進行驗證$_DVWA[ 'recaptcha_private_key' ], // 使用 DVWA 中設置的私鑰$_POST['g-recaptcha-response'] // 獲取表單中用戶填寫的 CAPTCHA 響應);// Did the CAPTCHA fail?if( !$resp ) { // 如果 CAPTCHA 驗證失敗// What happens when the CAPTCHA was entered incorrectly$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>"; // 顯示錯誤信息$hide_form = false; // 顯示表單return; // 停止執行}else {// CAPTCHA was correct. Do both new passwords match?if( $pass_new == $pass_conf ) { // 如果兩個密碼一致// Show next stage for the userecho "<pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre> // CAPTCHA 驗證成功提示<form action=\"#\" method=\"POST\"> // 第二步的確認修改表單<input type=\"hidden\" name=\"step\" value=\"2\" /> // 標記為第2步<input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" /> // 隱藏域保存新密碼<input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" /> // 隱藏域保存確認密碼<input type=\"hidden\" name=\"passed_captcha\" value=\"true\" /> // 隱藏域標記 CAPTCHA 已通過<input type=\"submit\" name=\"Change\" value=\"Change\" /> // 提交按鈕</form>";}else {// Both new passwords do not match.$html .= "<pre>Both passwords must match.</pre>"; // 顯示密碼不一致的錯誤信息$hide_form = false; // 顯示表單}}
}if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) { // 如果當前為第2步// Hide the CAPTCHA form$hide_form = true; // 隱藏 CAPTCHA 表單// Get input$pass_new = $_POST[ 'password_new' ]; // 獲取新密碼$pass_conf = $_POST[ 'password_conf' ]; // 獲取確認密碼// Check to see if they did stage 1if( !$_POST[ 'passed_captcha' ] ) { // 如果未通過 CAPTCHA 驗證(沒有設置 passed_captcha 字段)$html .= "<pre><br />You have not passed the CAPTCHA.</pre>"; // 提示用戶未通過 CAPTCHA$hide_form = false; // 顯示表單return; // 停止執行}// Check to see if both password matchif( $pass_new == $pass_conf ) { // 如果兩個密碼一致// They do!$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); // 對密碼進行轉義防止 SQL 注入$pass_new = md5( $pass_new ); // 將密碼使用 md5 加密// Update database$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';"; // 構造 SQL 更新語句$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); // 執行 SQL 并在出錯時輸出錯誤信息// Feedback for the end userecho "<pre>Password Changed.</pre>"; // 提示密碼修改成功}else {// Issue with the passwords matchingecho "<pre>Passwords did not match.</pre>"; // 提示密碼不一致$hide_form = false; // 顯示表單}((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); // 關閉數據庫連接
}?>
medium 級別與 low 級別的區別是:
-
增加了對 CAPTCHA 驗證通過的狀態存儲:通過隱藏字段
passed_captcha
傳遞。 -
在第二步執行修改前,檢查
passed_captcha
是否存在,以避免跳過 CAPTCHA 驗證。
不過這仍然可以被偽造表單繞過(如構造 POST 請求帶 passed_captcha=true
)
這里的話還是可以通過抓包添加這個字段來提交,顯示修改成功,這里的修改也是在repeater模塊中修改,然后點擊send
三、high級別源碼分析
?查看源碼,服務器的驗證邏輯是當$resp
是true
,并且參數recaptcha_response_field
等于hidd3n_valu3
(或者http包頭的User-Agent
參數等于reCAPTCHA)
時,就認為驗證碼輸入正確,反之錯誤。
<?php// 如果用戶提交了 "Change" 表單
if( isset( $_POST[ 'Change' ] ) ) {// 隱藏 CAPTCHA 表單$hide_form = true;// 獲取用戶輸入的新密碼和確認密碼$pass_new = $_POST[ 'password_new' ]; // 新密碼$pass_conf = $_POST[ 'password_conf' ]; // 確認密碼// 調用 reCAPTCHA 第三方函數驗證用戶輸入$resp = recaptcha_check_answer($_DVWA[ 'recaptcha_private_key' ], // 使用私鑰$_POST['g-recaptcha-response'] // 用戶輸入的驗證碼響應);// 如果驗證碼驗證成功,或使用了一個特定的隱藏值(后門驗證碼)并且UA是'reCAPTCHA'(模擬機器人)if ($resp || ($_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3' // 這是個后門值,意味著攻擊者可以繞過驗證碼&& $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA' // 并且 User-Agent 被偽裝成 reCAPTCHA)){// 如果兩個密碼相同if ($pass_new == $pass_conf) {// 使用 mysqli_real_escape_string 防止 SQL 注入(前提是數據庫連接存在)$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));$pass_new = md5( $pass_new ); // 使用 MD5 對密碼進行哈希(不安全)// 構造 SQL 更新語句,修改當前登錄用戶的密碼$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "' LIMIT 1;";// 執行 SQL 語句,如果失敗則輸出錯誤$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );// 提示用戶密碼修改成功echo "<pre>Password Changed.</pre>";} else {// 如果兩個密碼不一致,提示錯誤$html .= "<pre>Both passwords must match.</pre>";$hide_form = false;}} else {// 如果驗證碼驗證失敗,提示錯誤$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";$hide_form = false;return;}// 關閉數據庫連接((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}// 生成 Anti-CSRF token,防止 CSRF 攻擊
generateSessionToken();?>
安全點說明:
-
? 高等級增加了限制:必須通過 reCAPTCHA 驗證或匹配特定值且 User-Agent 正確(用于防御簡單的自動腳本)。
-
? 后門邏輯存在:驗證碼繞過后門
'hidd3n_valu3'
+'reCAPTCHA'
的組合,可以被攻擊者利用。 -
? 仍使用 MD5 加密密碼:MD5 不安全,容易被破解,建議使用 bcrypt 或 Argon2。
-
? 數據庫使用了過時接口:代碼基于
mysqli_*
和老舊錯誤處理方式,存在維護問題。
- 由于
$resp
參數我們無法控制,所以重心放在參數recaptcha_response_field、User-Agent
上。 - 修改參數,點擊
Send
:顯示修改成功。
?以上繞過方式確實是看了 DVWA 的源碼才找到的“后門繞過條件”,這在真實世界中屬于**“白盒滲透”,而大多數實際攻擊是“黑盒測試”**,不能看到源碼,就要靠邏輯推理 + 測試來逐步發現漏洞。在真實的 Web 安全測試中,驗證碼繞過是一項難度較高的工作,需要你善于分析流程、測試請求邏輯、借助自動化工具,不能依賴看源碼“取巧”,而是要推演漏洞邏輯 + 試錯測試。
四、impossible級別源碼分析
<?php// 如果點擊了“Change”按鈕(提交了修改密碼的表單)
if (isset($_POST['Change'])) {// 1?? 校驗 CSRF Token,防止跨站請求偽造checkToken($_REQUEST['user_token'], $_SESSION['session_token'], 'index.php');// 表示驗證碼驗證通過后不再顯示表單(控制顯示)$hide_form = true;// 2?? 獲取用戶輸入的新密碼,并進行處理(去轉義 + SQL安全 + 哈希)$pass_new = $_POST['password_new'];$pass_new = stripslashes($pass_new); // 去除反斜杠$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new) : trigger_error(...)); // SQL轉義,防止注入$pass_new = md5($pass_new); // 對新密碼進行 MD5 加密// 3?? 同樣處理確認密碼字段$pass_conf = $_POST['password_conf'];$pass_conf = stripslashes($pass_conf);$pass_conf = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_conf) : trigger_error(...));$pass_conf = md5($pass_conf);// 4?? 當前密碼校驗處理(確保用戶是本人)$pass_curr = $_POST['password_current'];$pass_curr = stripslashes($pass_curr);$pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_curr) : trigger_error(...));$pass_curr = md5($pass_curr);// 5?? 驗證 Google reCAPTCHA,確保用戶是真人操作$resp = recaptcha_check_answer($_DVWA['recaptcha_private_key'], // 使用 DVWA 配置中的私鑰$_POST['g-recaptcha-response'] // 獲取用戶提交的驗證碼響應 token);// 6?? 判斷驗證碼是否驗證通過if (!$resp) {echo "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";$hide_form = false; // 顯示表單重新輸入} else {// 7?? 驗證當前密碼是否正確(從數據庫中查找該用戶+密碼是否存在)$data = $db->prepare('SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;');$data->bindParam(':user', dvwaCurrentUser(), PDO::PARAM_STR);$data->bindParam(':password', $pass_curr, PDO::PARAM_STR);$data->execute();// 8?? 如果新密碼一致,且當前密碼正確,就更新數據庫密碼if (($pass_new == $pass_conf) && ($data->rowCount() == 1)) {$data = $db->prepare('UPDATE users SET password = (:password) WHERE user = (:user);');$data->bindParam(':password', $pass_new, PDO::PARAM_STR);$data->bindParam(':user', dvwaCurrentUser(), PDO::PARAM_STR);$data->execute();echo "<pre>Password Changed.</pre>"; // 成功提示} else {echo "<pre>Either your current password is incorrect or the new passwords did not match.<br />Please try again.</pre>";$hide_form = false;}}
}// 9?? 頁面底部重新生成 Anti-CSRF token,用于下次提交
generateSessionToken();?>
總結:Impossible級別的安全設計
安全點 | 說明 |
---|---|
? CSRF 防護 | 使用 user_token 和 session 進行校驗 |
? SQL 注入防護 | 使用 PDO 預處理語句,避免 SQL 注入 |
? XSS 防護 | 未涉及 DOM 輸出,但數據未直接回顯,隱患小 |
? CAPTCHA 驗證 | 使用 Google reCAPTCHA,服務端校驗是否通過 |
? 身份驗證 | 驗證當前密碼是否正確,防止他人盜改密碼 |
? 表單邏輯清晰 | 分支判斷詳細,失敗時不暴露具體原因(只有簡單提示) |