Insecure CAPTCHA(不安全的驗證碼)
Insecure CAPTCHA(不安全的驗證碼),CAPTCHA全稱為Completely Automated Public Turing Test to Tell Computers and Humans Apart,中文名字是全自動區分計算機和人類的圖靈測試。關於這一項,其實是在驗證流程出現了邏輯漏洞。
驗證流程:
用戶首先訪問網頁,觸發頁面的驗證碼的js模塊,向谷歌服務器發起請求,谷歌服務器將驗證碼發給用戶。用戶輸入驗證碼發送數據回去,這里發給的是訪問網站的服務器,網站的服務器拿到驗證碼后,再去訪問谷歌的服務器,谷歌的服務器會判斷驗證碼是否正確,再將結果返回給網站服務器。
Insecure CAPTCHA主題:
Low
源碼解析
<?php //第一階段,驗證身份,驗證階段為step 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 //使用第三方進行身份驗證 //recaptcha_check_answer($privkey,$remoteip, $challenge,$response) 參數$privkey是服務器申請的private key,$remoteip是用戶的ip,$challenge是recaptcha_challenge_field字段的值,來自前端頁面 ,$response是recaptcha_response_field字段的值。函數返回ReCaptchaResponse class的實例,ReCaptchaResponse類有2個屬性 : $is_valid是布爾型的,表示校驗是否有效, $error是返回的錯誤代碼。 $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 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 { // 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 echo "<pre>Password Changed.</pre>"; } else { // Issue with the passwords matching echo "<pre>Passwords did not match.</pre>"; $hide_form = false; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
漏洞復現
按照源碼分析,總共分為兩個階段:
第一階段:對用戶的身份進行驗證,step為1,驗證成功后才能進行密碼修改
第二階段:step為2,兩次輸入的密碼一致,可以進行修改
考慮:直接跳過第一階段,輸入的密碼一致,是否可以?
(1)輸入兩個一致的密碼,直接使用burp抓包
(2)將step改為2,進行測試
(3)修改成功
這種可以參考CSRF模塊,都可以去構造一個Web站點,做一個誘導頁面,將step設置為2,誘導用戶去點擊。
Medium
源碼解析
<?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 echo " // 對參數passed_captcha進行驗證,如果通過身份驗證,該參數就為true <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 echo "<pre>Password Changed.</pre>"; } else { // Issue with the passwords matching echo "<pre>Passwords did not match.</pre>"; $hide_form = false; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
漏洞復現
與Low難度相比,增加了一個passed_capt,當passed_capt為true時就可以修改密碼了
(1)同樣使用burp進行抓包
(2)修改step為2,后面增加參數passed_captcha=true
(3)修改成
High
源碼解析
<?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'] ); //(通過身份驗證條件)或者 (參數g-recaptcha-respon為hidd3n_valu3並且參數 HTTP_USER_AGE為 reCAPTC)就算是驗證通過了 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 echo "<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(); ?>
漏洞復現
可以看到,驗證過程已經不分步走了,都合在一個階段里面了
服務器的驗證邏輯是當$resp(這里是指谷歌返回的驗證結果)是false,並且參數recaptcha_response_field不等於hidd3n_valu3(或者http包頭的User-Agent參數不等於reCAPTCHA)時,就認為驗證碼輸入錯誤,反之則認為已經通過了驗證碼的檢查。
搞清楚了驗證邏輯,剩下就是偽造繞過了,由於$resp參數我們無法控制,所以重心放在參數recaptcha_response_field、User-Agent上。
(1)burp抓包
(2)修改參數 $_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3' $_SERVER[ 'HTTP_USER_AGENT' == 'reCAPTCHA'
(3)修改成功
Impossible
源碼解析
<?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 echo "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>"; $hide_form = false; return; } 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! echo "<pre>Password Changed.</pre>"; } else { // Feedback for the end user - failed! echo "<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注入,驗證過程不再分成兩部分了,同時要求用戶輸入之前的密碼,進一步加強了身份認證。