Insecure CAPTCHA(不安全的驗證碼),CAPTCHA全程為Completely Automated Public Turing Test to Tell Computers and Humans Apart(全自動區分計算機和人類的圖靈測試)的簡稱。這一模組的內容叫做不安全的驗證流程比較好,主要是驗證流程出現了邏輯漏洞,而不是谷歌的驗證碼有問題。這一模組驗證碼使用的是Google提供的reCAPTCHA服務,以下是驗證具體的流程。
伺服器通過呼叫recaptcha_check_answer函數檢查使用者輸入的正確性。
recaptcha_check_answer($privkey,$remoteip,$challenge,$response)
引數$privey是伺服器申請的private key,$remoteip是使用者的IP,$challenge是recaptcha_challenge_field欄位的值,來自前端頁面,$response是recaptcha_response_field欄位的值。函數返回ReCaptchaResponse class的範例,ReCaptchaResponse類有2個屬性:
$is_valid是布林型的,表示校驗是否有效。
$error是返回的錯誤程式碼。
這裡開啟DVWA靶場的Inseure CAPTCHA的時候,會發現上方有一行這樣的報錯「reCAPTCHA API key missing from config file: D:\phpstudy_pro\WWW\DVWA\config\config.inc.php」這種情況。
出現這種情況是因為使用reCAPTCHA沒有申請金鑰,因此需要手動填入金鑰,開啟提示的組態檔找到檔案「config.inc.php」,然後我們找到如下:
$_DVWA[ 'recaptcha_public_key' ] = ' '; $_DVWA[ 'recaptcha_private_key' ] = ' ';
然後將他們改為如下,然後就可以儲存了,最後就可以開始進行實戰專案了。
$_DVWA[ 'recaptcha_public_key' ] = '6LdK7xITAAzzAAJQTfL7fu6I-0aPl8KHHieAT_yJg'; $_DVWA[ 'recaptcha_private_key' ] = '6LdK7xITAzzAAL_uw9YXVUOPoIHPZLfw2K1n5NVQ';
程式碼分析:
<?php if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) { // Hide the CAPTCHA form $hide_form = true; // Get input $pass_new = $_POST[ 'password_new' ]; $pass_conf = $_POST[ 'password_conf' ]; // Check CAPTCHA from 3rd party $resp = recaptcha_check_answer( $_DVWA[ 'recaptcha_private_key'], $_POST['g-recaptcha-response'] ); // Did the CAPTCHA fail? if( !$resp ) { // 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 user $html .= " <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 { // Both new passwords do not match. $html .= "<pre>Both passwords must match.</pre>"; $hide_form = false; } } } if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) { // Hide the CAPTCHA form $hide_form = true; // Get input $pass_new = $_POST[ 'password_new' ]; $pass_conf = $_POST[ 'password_conf' ]; // Check to see if both password match if( $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)) ? "" : "")); $pass_new = md5( $pass_new ); // Update database $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>' ); // Feedback for the end user $html .= "<pre>Password Changed.</pre>"; } else { // Issue with the passwords matching $html .= "<pre>Passwords did not match.</pre>"; $hide_form = false; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
這裡修改密碼一共有兩個步驟,第一步是CAPTCHA的驗證環節,第二步是將引數POST到後臺。由於兩步操作完全是分開的,沒有聯絡,於是我們可以忽略第一步的驗證,直接提交修改申請。兩個步驟對應的step引數不同,可以通過抓取報文並且修改step,來實現的驗證繞過。程式碼沒有對CSRF進行任何的防護,可以利用CSRF進行攻擊。
我們開始進行攻擊,我們思路就是將第一步跳過直接進行第二步。所以我們這裡將輸入兩個一致的密碼不進行驗證,直接用Burpsuite進行抓包操作。
將「step1」修改為「step2」重新傳送後,得到以下介面,說明修改成功。
程式碼分析:
<?php if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) { // Hide the CAPTCHA form $hide_form = true; // Get input $pass_new = $_POST[ 'password_new' ]; $pass_conf = $_POST[ 'password_conf' ]; // Check CAPTCHA from 3rd party $resp = recaptcha_check_answer( $_DVWA[ 'recaptcha_private_key' ], $_POST['g-recaptcha-response'] ); // Did the CAPTCHA fail? if( !$resp ) { // 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 user $html .= " <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=\"hidden\" name=\"passed_captcha\" value=\"true\" /> <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' ) ) { // Hide the CAPTCHA form $hide_form = true; // Get input $pass_new = $_POST[ 'password_new' ]; $pass_conf = $_POST[ 'password_conf' ]; // Check to see if they did stage 1 if( !$_POST[ 'passed_captcha' ] ) { $html .= "<pre><br />You have not passed the CAPTCHA.</pre>"; $hide_form = false; return; } // Check to see if both password match if( $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)) ? "" : "")); $pass_new = md5( $pass_new ); // Update database $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>' ); // Feedback for the end user $html .= "<pre>Password Changed.</pre>"; } else { // Issue with the passwords matching $html .= "<pre>Passwords did not match.</pre>"; $hide_form = false; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
我們可以看出基於Low級別,在第二步增加了第一步是否通過的驗證,即判斷引數passed_captcha是否為真。Passed_captcha引數是通過POST提交的,整個請求也就是POST請求,故可以人為加上此引數。
接下來我們開始攻擊,一共也是分兩步,但是不同於Low,我們這次使用Burpsuite開始修改報文,將步驟直接調整到第二步,第二步的驗證偽造為已驗證。然後我們直接加入passed_captcha引數,混入POST引數提交。將「step=1」改為「step=2」,然後加上「&passed_capcha=true」即可。
放包以後,我們可以觀察到頁面顯示「Password Change」成功即是完成。
程式碼分析:
<?php if( isset( $_POST[ 'Change' ] ) ) { // Hide the CAPTCHA form $hide_form = true; // Get input $pass_new = $_POST[ 'password_new' ]; $pass_conf = $_POST[ 'password_conf' ]; // Check CAPTCHA from 3rd party $resp = recaptcha_check_answer( $_DVWA[ 'recaptcha_private_key' ], $_POST['g-recaptcha-response'] ); if ( $resp || ( $_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3' && $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA' ) ){ // CAPTCHA was correct. Do both new passwords match? if ($pass_new == $pass_conf) { $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 ); // Update database $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "' LIMIT 1;"; $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>' ); // Feedback for user $html .= "<pre>Password Changed.</pre>"; } else { // Ops. Password mismatch $html .= "<pre>Both passwords must match.</pre>"; $hide_form = false; } } else { // What happens when the CAPTCHA was entered incorrectly $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); } // Generate Anti-CSRF token generateSessionToken(); ?>
High級別將驗證流程合併,通過連續的判斷將兩個步驟相同的部分合並,避免第一步驗證的直接改參繞過。加入了token機制,有效防止了CSRF漏洞攻擊,下面不再做攻擊頁面。
我們開始攻擊,看到了後端程式碼發現了,及時不驗證也有機會繞過驗證,於是針對g-recaptcha-response和HTTP_USER_AGENT操作。
同樣不驗證,直接提交請求對相關引數進行抓包修改。
程式碼分析:
<?php if( isset( $_POST[ 'Change' ] ) ) { // Check Anti-CSRF token checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // Hide the CAPTCHA form $hide_form = true; // Get input $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("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $pass_new = md5( $pass_new ); $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("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $pass_conf = md5( $pass_conf ); $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("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : "")); $pass_curr = md5( $pass_curr ); // Check CAPTCHA from 3rd party $resp = recaptcha_check_answer( $_DVWA[ 'recaptcha_private_key' ], $_POST['g-recaptcha-response'] ); // Did the CAPTCHA fail? if( !$resp ) { // What happens when the CAPTCHA was entered incorrectly $html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>"; $hide_form = false; } else { // Check that the current password is correct $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(); // Do both new password match and was the current password correct? if( ( $pass_new == $pass_conf) && ( $data->rowCount() == 1 ) ) { // Update the database $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(); // Feedback for the end user - success! $html .= "<pre>Password Changed.</pre>"; } else { // Feedback for the end user - failed! $html .= "<pre>Either your current password is incorrect or the new passwords did not match.<br />Please try again.</pre>"; $hide_form = false; } } } // Generate Anti-CSRF token generateSessionToken(); ?>
Impossible的程式碼作為防禦模板,使用Anti-CSRF token機制防禦CSRF攻擊。驗證步驟合併為同一步,無需分開,使得驗證環節無法繞過。要求輸入修改之前的密碼,攻擊者無法繞過。利用PDO技術輸入內容過濾,防止了sql注入。